pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedDeclarative partitioning - another take
329+ messages / 36 participants
[nested] [flat]
* Declarative partitioning - another take
@ 2016-08-10 11:09 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 5 replies; 329+ messages in thread
From: Amit Langote @ 2016-08-10 11:09 UTC (permalink / raw)
To: pgsql-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:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables.patch (116.2K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables.patch)
download | inline diff:
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
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables.patch (22.8K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables.patch)
download | inline diff:
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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions.patch (196.3K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions.patch)
download | inline diff:
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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions.patch (17.8K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions.patch)
download | inline diff:
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
[text/x-diff] 0005-Teach-a-few-places-to-use-partition-check-constraint.patch (17.2K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/6-0005-Teach-a-few-places-to-use-partition-check-constraint.patch)
download | inline diff:
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
[text/x-diff] 0006-Introduce-a-PartitionTreeNode-data-structure.patch (8.5K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/7-0006-Introduce-a-PartitionTreeNode-data-structure.patch)
download | inline diff:
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
[text/x-diff] 0007-Tuple-routing-for-partitioned-tables.patch (39.8K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/8-0007-Tuple-routing-for-partitioned-tables.patch)
download | inline diff:
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
[text/x-diff] 0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel.patch (24.7K, ../../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/9-0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel.patch)
download | inline diff:
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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-10 18:22 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
4 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-08-10 18:22 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Attached is the latest set of patches to implement declarative
> partitioning.
Cool. I would encourage you to give some thought to what is the least
committable subset of these patches, and think about whether it can be
reorganized to make that smaller. Based on the descriptions, it
sounds to me like the least committable subset is probably all of 0001
- 0005 as one giant commit, and that's a lot of code. Maybe there's
no real way to come up with something more compact than that, but it's
worth some thought.
> 0001-Catalog-and-DDL-for-partition-key.patch
+ <entry><link
linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
pg_partitioned seems like a slightly strange choice of name. We have
no other catalog tables whose names end in "ed", so the use of a past
participle here is novel. More generally, this patch seems like it's
suffering a bit of terminological confusion: while the catalog table
is called pg_partitioned, the relkind is RELKIND_PARTITION_REL. And
get_relation_by_qualified_name() thinks that RELKIND_PARTITION_REL is
a "table", but getRelationDescription thinks it's a "partitioned
table". I think we need to get all these bits and pieces on the same
page, or have some kind of consistent way of deciding what to do in
each case. Maybe pg_partitioned_table, RELKIND_PARTITIONED_TABLE,
etc. for the internal stuff, but just "table" in user-facing messages.
Alternatively, I wonder if we should switch to calling this a
"partition root" rather than a "partitioned table". It's not the
whole table, just the root. And doesn't contain data - in fact it
probably shouldn't even have storage - so calling it a "table" might
be confusing. But if we're using CREATE TABLE to create it in the
ifrst place, then calling it something other than a table later on is
also confusing. Hmm.
+ <entry><structfield>partexprs</structfield></entry>
There's a certain symmetry between this and what we do for indexes,
but I'm wondering whether there's a use case for partitioning a table
by an expression rather than a column value. I suppose if you've
already done the work, there's no harm in supporting it.
+[ PARTITION BY {RANGE | LIST} ( { <replaceable
class="parameter">column_name</replaceable> | ( <replaceable
class="parameter">expression</replaceable> ) } [ <replaceable
class="parameter">opclass</replaceable> ] [, ...] )
The spacing isn't right here. Should say { RANGE | LIST } rather than
{RANGE|LIST}. Similarly elsewhere.
+ 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.
Why do we support range partitioning with multiple columns, but list
partitioning only with a single column?
+ The type of a key column or an expresion must have an associated
Typo.
+ <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>
But I presume you can define these constraints on the partitions;
that's probably worth mentioning. I'd say something like this
"Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or
FOREIGN KEY constraints; however, you can define these constraints on
individual data partitions."
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
here. Instead, you could just add an additional Boolean argument to
recordDependencyOnSingleRelExpr. That seems less likely to create
bugs in unrelated portions of the code.
+ /*
+ * 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);
This seems extremely unlikely to be the right way to do this. We're
just doing parse analysis here, so depending on properties of the
table that are (or might be made to be) changeable is not good. It's
also an extra catalog lookup whether the table is partitioned or not
(and whether rte->inh is already true or not). Furthermore, it seems
to go against the comment explaining what rte->inh is supposed to
mean, which says:
* inh is TRUE for relation references that should be expanded to include
* inheritance children, if the rel has any. This *must* be FALSE for
* RTEs other than RTE_RELATION entries.
I am not sure what problem you're trying to solve here, but I suspect
this needs to be ripped out altogether or handled later, during
planning. Similarly for the related change in addRangeTableEntry.
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
I'd probably cut the initial size of this down a bit. Odds are good
that not all of the tables you access will be partitioned. Of course
that assumes we'll avoid probing this syscache for non-partitioned
tables, but I think that's pretty important.
+ 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")));
Maybe "cannot alter type of column named in partition key"? And
similarly for the other one.
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
Shouldn't this be in src/backend/parser/parse_*.c instead of tablecmds.c?
Most of this (0001) looks pretty reasonable. I'm sure that there is
more that needs fixing than what I've mentioned above, which is just
what I saw on an initial read-through, but overall I like the
approach.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-15 11:21 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-08-15 11:21 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
Thanks a lot for taking a look at this.
On 2016/08/11 3:22, Robert Haas wrote:
> On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Attached is the latest set of patches to implement declarative
>> partitioning.
>
> Cool. I would encourage you to give some thought to what is the least
> committable subset of these patches, and think about whether it can be
> reorganized to make that smaller. Based on the descriptions, it
> sounds to me like the least committable subset is probably all of 0001
> - 0005 as one giant commit, and that's a lot of code. Maybe there's
> no real way to come up with something more compact than that, but it's
> worth some thought.
I will consider this and try to come up with a minimal patch set covering
what is now 0001 - 0005.
>> 0001-Catalog-and-DDL-for-partition-key.patch
>
> + <entry><link
> linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
>
> pg_partitioned seems like a slightly strange choice of name. We have
> no other catalog tables whose names end in "ed", so the use of a past
> participle here is novel. More generally, this patch seems like it's
> suffering a bit of terminological confusion: while the catalog table
> is called pg_partitioned, the relkind is RELKIND_PARTITION_REL. And
> get_relation_by_qualified_name() thinks that RELKIND_PARTITION_REL is
> a "table", but getRelationDescription thinks it's a "partitioned
> table". I think we need to get all these bits and pieces on the same
> page, or have some kind of consistent way of deciding what to do in
> each case. Maybe pg_partitioned_table, RELKIND_PARTITIONED_TABLE,
> etc. for the internal stuff, but just "table" in user-facing messages.
Name pg_partitioned does sound a little strange now that you mention it.
I agree to make the terminology more consistent and to that end, your
suggestion to keep the user-facing term "table" and using
pg_partitioned_table and RELKIND_PARTITIONED_TABLE for internals sounds good.
> Alternatively, I wonder if we should switch to calling this a
> "partition root" rather than a "partitioned table". It's not the
> whole table, just the root. And doesn't contain data - in fact it
> probably shouldn't even have storage - so calling it a "table" might
> be confusing. But if we're using CREATE TABLE to create it in the
> ifrst place, then calling it something other than a table later on is
> also confusing. Hmm.
I think it makes sense to keep calling it a table because it has all the
logical properties of a table even though it will differ from a regular
table on the basis of physical implementation details such as that it does
not own physical storage. Am I missing something?
>
> + <entry><structfield>partexprs</structfield></entry>
>
> There's a certain symmetry between this and what we do for indexes,
> but I'm wondering whether there's a use case for partitioning a table
> by an expression rather than a column value. I suppose if you've
> already done the work, there's no harm in supporting it.
Yeah, it's not a whole lot of code to manage expressions alongside simple
column references.
> +[ PARTITION BY {RANGE | LIST} ( { <replaceable
> class="parameter">column_name</replaceable> | ( <replaceable
> class="parameter">expression</replaceable> ) } [ <replaceable
> class="parameter">opclass</replaceable> ] [, ...] )
>
> The spacing isn't right here. Should say { RANGE | LIST } rather than
> {RANGE|LIST}. Similarly elsewhere.
Will fix.
> + 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.
>
> Why do we support range partitioning with multiple columns, but list
> partitioning only with a single column?
This is mostly because I didn't find any other database supporting it and
hence thought maybe there is not much use for it.
On the implementation side, I think it makes sense to think of ordering of
tuples (for a multi-column range key), where we compare a new tuple's
partition key columns one-by-one until we find the column such that its
value != the value of corresponding column in the range upper bound of a
partition (usually but not necessarily the last column of the key). Once
we have determined which side of the bound the value was, we then perform
the same process with either the lower bound of the same partition or
upper bound of some partition in the other half as determined by binary
search logic.
Conversely, when determining the constraint on rows a range partition
contains, we emit a (keycol = lowerval) expression for all columns i =
0..j-1 where range.lowerval[i] = range.upperval[i] and then emit (keycol >
/ >= range.lowerval[j] and keycol < / <= upperval[j]) for the first column
j where lowerval[j] < upperval[j] and stop at that column (any further
columns are irrelevant).
When considering the same for list partitioning where we consider set-
membership of values (based on equality of a new tuple's value for the
column and a partition's list of values), implementing multi-column logic
does not seem as straightforward. Also, it might make the optimizations
we recently discussed slightly more complicated to implement. Although I
may be missing something.
> + The type of a key column or an expresion must have an associated
>
> Typo.
Will fix.
>
> + <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>
>
> But I presume you can define these constraints on the partitions;
> that's probably worth mentioning. I'd say something like this
> "Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or
> FOREIGN KEY constraints; however, you can define these constraints on
> individual data partitions."
Okay, will mention that.
> + if (partexprs)
> + recordDependencyOnSingleRelExpr(&myself,
> + (Node *) partexprs,
> + RelationGetRelid(rel),
> + DEPENDENCY_NORMAL,
> + DEPENDENCY_IGNORE);
>
> I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
> here. Instead, you could just add an additional Boolean argument to
> recordDependencyOnSingleRelExpr. That seems less likely to create
> bugs in unrelated portions of the code.
I did consider a Boolean argument instead of a new DependencyType value,
however it felt a bit strange to pass a valid value for the fifth argument
(self_behavior) and then ask using a separate parameter that it (a
self-dependency) is to be ignored. By the way, no pg_depend entry is
created on such a call, so the effect of the new type's usage seems
localized to me. Thoughts?
>
> + /*
> + * 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);
>
> This seems extremely unlikely to be the right way to do this. We're
> just doing parse analysis here, so depending on properties of the
> table that are (or might be made to be) changeable is not good. It's
> also an extra catalog lookup whether the table is partitioned or not
> (and whether rte->inh is already true or not). Furthermore, it seems
> to go against the comment explaining what rte->inh is supposed to
> mean, which says:
>
> * inh is TRUE for relation references that should be expanded to include
> * inheritance children, if the rel has any. This *must* be FALSE for
> * RTEs other than RTE_RELATION entries.
>
> I am not sure what problem you're trying to solve here, but I suspect
> this needs to be ripped out altogether or handled later, during
> planning. Similarly for the related change in addRangeTableEntry.
Okay, I will see how this could rather be done within the planner. My
intention here is to keep the code churn low by not modifying a lot of
places in the code where rte->inh is checked to turn inheritance on or
off. I agree though that it's a bit ugly and perhaps wrong.
> + {PartitionedRelationId, /* PARTEDRELID */
> + PartitionedRelidIndexId,
> + 1,
> + {
> + Anum_pg_partitioned_partedrelid,
> + 0,
> + 0,
> + 0
> + },
> + 128
>
> I'd probably cut the initial size of this down a bit. Odds are good
> that not all of the tables you access will be partitioned. Of course
> that assumes we'll avoid probing this syscache for non-partitioned
> tables, but I think that's pretty important.
Okay, I will turn that down to 64 maybe.
>
> + 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")));
>
> Maybe "cannot alter type of column named in partition key"? And
> similarly for the other one.
Will fix.
>
> +/*
> + * transformPartitionBy
> + * Transform any expressions present in the partition key
> + */
> +static PartitionBy *
> +transformPartitionBy(Relation rel, PartitionBy *partitionby)
>
> Shouldn't this be in src/backend/parser/parse_*.c instead of tablecmds.c?
It can be performed only after the new relation is opened which can be
only after we have called heap_create_with_catalog() which is in
tablecmds.c: DefineRelation(). I think that's because we call
transformExpr() on partition expressions which expects to be able to see
the table's columns. Other callers such as transformIndexStmt and
transformAlterTableStmt can do transformations within parse_utilcmds.c
because they can open the relation there.
>
> Most of this (0001) looks pretty reasonable. I'm sure that there is
> more that needs fixing than what I've mentioned above, which is just
> what I saw on an initial read-through, but overall I like the
> approach.
>
Okay, thanks. I will post the updated patches soon.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-16 06:30 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-08-16 06:30 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
> I think it makes sense to keep calling it a table because it has all the
> logical properties of a table even though it will differ from a regular
> table on the basis of physical implementation details such as that it does
> not own physical storage. Am I missing something?
>
> >
> > + <entry><structfield>partexprs</structfield></entry>
> >
> > There's a certain symmetry between this and what we do for indexes,
> > but I'm wondering whether there's a use case for partitioning a table
> > by an expression rather than a column value. I suppose if you've
> > already done the work, there's no harm in supporting it.
>
> Yeah, it's not a whole lot of code to manage expressions alongside simple
> column references.
>
Users who would like to partition their tables by "age" will partition
those by the month or year extracted out of a date column e.g. order_date.
They will find it convenient to use an expression (extract(month from
date)) as a partition key, instead of storing month or year as a separate
column.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-16 14:05 Robert Eckhardt <reckhardt@pivotal.io>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Eckhardt @ 2016-08-16 14:05 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Tue, Aug 16, 2016 at 2:30 AM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:
>
>
>
>
>> I think it makes sense to keep calling it a table because it has all the
>> logical properties of a table even though it will differ from a regular
>> table on the basis of physical implementation details such as that it does
>> not own physical storage. Am I missing something?
>>
>> >
>> > + <entry><structfield>partexprs</structfield></entry>
>> >
>> > There's a certain symmetry between this and what we do for indexes,
>> > but I'm wondering whether there's a use case for partitioning a table
>> > by an expression rather than a column value. I suppose if you've
>> > already done the work, there's no harm in supporting it.
>>
>> Yeah, it's not a whole lot of code to manage expressions alongside simple
>> column references.
>>
>
> Users who would like to partition their tables by "age" will partition
> those by the month or year extracted out of a date column e.g. order_date.
> They will find it convenient to use an expression (extract(month from
> date)) as a partition key, instead of storing month or year as a separate
> column.
>
In GPDB we have partitioning. It is almost always by date and then often
the partitions are for different sizes, i.e. by day for 30 days then by
month for 3 years then by year. What we also support, but isn't super
performant, is sub-partitioning.
This is where some on the newer indexing strategies is interesting to me. I
see them as synergistic not redundant.
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
>
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-16 18:54 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
4 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-08-16 18:54 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> 0002-psql-and-pg_dump-support-for-partitioned-tables.patch
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
Version check is redundant, right?
+) 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)))
I understand that it's probably difficult not to end up with two sets
of parentheses here, but can we avoid ending up with three sets?
Also, I wonder if pg_get_partkeydef() should omit "PARTITION BY" and
pg_dump can add that part back. Then this could say:
Partition Key: RANGE ((a + b))
...which seems a good deal more natural than what you have now.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-16 20:29 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
4 siblings, 2 replies; 329+ messages in thread
From: Robert Haas @ 2016-08-16 20:29 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> 0003-Catalog-and-DDL-for-partition-bounds.patch
>
> 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.
Hmm, I don't think I like this. Why should it be necessary to detach
a partition before dropping it? That seems like an unnecessary step.
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY
IMMEDIATE ]
+
</synopsis>
Unnecessary hunk.
+ <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>
Sentence fragment.
+ <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>
This is another restriction I don't understand. Why can't I attach a
partitioned table?
+ 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.
Ugh, why? I think this should work exactly the same way for
partitioned tables that it does for any other inheritance hierarchy.
Sure, you'll get no rows, but who cares?
+CREATE FOREIGN TABLE measurement_y2016m07
+ PARTITION OF measurement FOR VALUES START ('2016-07-01') END
('2016-08-01');
+ SERVER server_07;
Extra semicolon?
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
I think experience suggests that this is a good restriction, but then
why does the syntax synopsis indicate that PARTITION BY can be
specified along with column definitions? Similarly for CREATE FOREIGN
TABLE.
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
This is not very clear.
- /* 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;
It might be right to disallow NO INHERIT in this case, but I don't
think it can be right to just silently ignore it.
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
This seems unlikely to be safe, unless I'm missing something.
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
This is unsafe. After systable_endscan, it is no longer OK to access
form->inhparent.
Try building with CLOBBER_CACHE_ALWAYS to find other cache flush hazards.
There should probably be a note in the function header comment that it
is unsafe to use this for an inheritance child that is not a
partition, because there could be more than one parent in that case.
Or maybe the whole idea of this function just isn't very sound...
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
What's the point? If we're going to have a wrapper here at all, then
shouldn't it have a name that matches the existing convention - e.g.
find_partitions() or find_child_partitions()? But I think you might
as well just use find_inheritance_children() directly.
+ * Happens when we have created the pg_inherits entry
but not the
+ * pg_partition entry yet.
Why do we ever allow the flow of control to reach this point while we
are in such an intermediate state?
+free_partition_info(PartitionInfoData **p, int num)
Seems very error-prone. Isn't this why MemoryContextReset was invented?
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
This is used in a lot of places, and the overhead of checking it in
all of those places is not necessarily nil. Syscache lookups aren't
free. What if we didn't create a new catalog for this and instead
just added a relpartitionbound attribute to pg_class? It seems a bit
silly to have a whole extra catalog to store one extra column...
/*
+ * 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)));
+ }
Why? This seems like an entirely arbitrary prohibition. If inserts
aren't supported, then they'll fail at runtime. Same for updates or
deletes, for which you have not added checks. I think you should just
remove this.
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
Disagree with this, too. There's no reason for partitioned tables to
be special in this way. Similarly, disagree with all of the places
that do something similar.
- 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")));
You have a few of these; they cause problems for translators, because
different languages have different word ordering. Repeat the entire
message instead: is_attach_partition ? "column \"%s\" in source table
must be marked NOT NULL" : "column \"%s\" in child table must be
marked NOT NULL".
+-- XXX add ownership tests
So do that. :-)
+ERROR: column "b" is not null in parent
+HINT: Please drop not null in the parent instead
Hmm. That hint doesn't seem like project style, and I'm not sure
that it really makes sense to issue such a hint anyway. Who knows
whether that is the right thing to do? I think you should somehow be
complaining about the fact that this is a partition, rather than
complaining about the fact that the column is NOT NULL in the parent.
Are we insisting that the flags match exactly, or only that the child
may not allow nulls unless the parent does?
+ERROR: new partition's list of values overlaps with partition
"lpart1" of "list_parted"
Maybe just:
ERROR: partitions must not overlap
-or-
ERROR: partition "%s" would overlap partition "%s"
As before, this is just an initial read-through, so apologies for
whatever I may have missed.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-17 05:33 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-08-17 05:33 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
> +relid_is_partition(Oid relid)
> +{
> + return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
> +}
>
> This is used in a lot of places, and the overhead of checking it in
> all of those places is not necessarily nil. Syscache lookups aren't
> free. What if we didn't create a new catalog for this and instead
> just added a relpartitionbound attribute to pg_class? It seems a bit
> silly to have a whole extra catalog to store one extra column...
>
>
>
It looks like in most of the places where this function is called it's
using relid_is_partition(RelationGetRelid(rel)). Instead probably we should
check existence of rd_partdesc or rd_partkey within Relation() and make
sure that those members are always set for a partitioned table. That will
avoid cache lookup and may give better performance.
That brings up another question. Can we have rd_partdesc non null and
rd_partkey null or vice-versa. If not, should we club those into a single
structure like Partition (similar to Relation)?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-17 06:21 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-08-17 06:21 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On 2016/08/17 14:33, Ashutosh Bapat wrote:
>> +relid_is_partition(Oid relid)
>> +{
>> + return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
>> +}
>>
>> This is used in a lot of places, and the overhead of checking it in
>> all of those places is not necessarily nil. Syscache lookups aren't
>> free. What if we didn't create a new catalog for this and instead
>> just added a relpartitionbound attribute to pg_class? It seems a bit
>> silly to have a whole extra catalog to store one extra column...
>>
> It looks like in most of the places where this function is called it's
> using relid_is_partition(RelationGetRelid(rel)). Instead probably we should
> check existence of rd_partdesc or rd_partkey within Relation() and make
> sure that those members are always set for a partitioned table. That will
> avoid cache lookup and may give better performance.
It seems you are talking about a *partitioned* relation here, whereas
relid_is_partition() is to trying to check if a relation is *partition* by
looking up the pg_partition catalog (or the associated cache). For the
former, the test you suggest or rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE test is enough.
I am slightly tempted to eliminate the pg_partition catalog and associated
syscache altogether and add a column to pg_class as Robert suggested.
That way, all relid_is_partition() calls will be replaced by
rel->rd_partbound != NULL check. But one potential problem with that
approach is that now whenever a parent relation is opened, all the
partition relations must be opened to get the partbound value (to form the
PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
currently, we just look up the pg_partition catalog (or the associated
cache) for every partition and that gets us the partbound.
> That brings up another question. Can we have rd_partdesc non null and
> rd_partkey null or vice-versa. If not, should we club those into a single
> structure like Partition (similar to Relation)?
It's true that rd_partkey and rd_partdesc are both either NULL or
non-NULL, so combining them into a single struct is an idea worth considering.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-17 06:27 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-08-17 06:27 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Wed, Aug 17, 2016 at 11:51 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/08/17 14:33, Ashutosh Bapat wrote:
> >> +relid_is_partition(Oid relid)
> >> +{
> >> + return SearchSysCacheExists1(PARTRELID,
> ObjectIdGetDatum(relid));
> >> +}
> >>
> >> This is used in a lot of places, and the overhead of checking it in
> >> all of those places is not necessarily nil. Syscache lookups aren't
> >> free. What if we didn't create a new catalog for this and instead
> >> just added a relpartitionbound attribute to pg_class? It seems a bit
> >> silly to have a whole extra catalog to store one extra column...
> >>
> > It looks like in most of the places where this function is called it's
> > using relid_is_partition(RelationGetRelid(rel)). Instead probably we
> should
> > check existence of rd_partdesc or rd_partkey within Relation() and make
> > sure that those members are always set for a partitioned table. That will
> > avoid cache lookup and may give better performance.
>
> It seems you are talking about a *partitioned* relation here, whereas
> relid_is_partition() is to trying to check if a relation is *partition* by
> looking up the pg_partition catalog (or the associated cache). For the
> former, the test you suggest or rd_rel->relkind ==
> RELKIND_PARTITIONED_TABLE test is enough.
>
Uh, you are right. Sorry for my misunderstanding.
>
> I am slightly tempted to eliminate the pg_partition catalog and associated
> syscache altogether and add a column to pg_class as Robert suggested.
> That way, all relid_is_partition() calls will be replaced by
> rel->rd_partbound != NULL check. But one potential problem with that
> approach is that now whenever a parent relation is opened, all the
> partition relations must be opened to get the partbound value (to form the
> PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
> currently, we just look up the pg_partition catalog (or the associated
> cache) for every partition and that gets us the partbound.
>
> > That brings up another question. Can we have rd_partdesc non null and
> > rd_partkey null or vice-versa. If not, should we club those into a single
> > structure like Partition (similar to Relation)?
>
> It's true that rd_partkey and rd_partdesc are both either NULL or
> non-NULL, so combining them into a single struct is an idea worth
> considering.
>
> Thanks,
> Amit
>
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-17 20:23 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-08-17 20:23 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Aug 17, 2016 at 2:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> I am slightly tempted to eliminate the pg_partition catalog and associated
> syscache altogether and add a column to pg_class as Robert suggested.
> That way, all relid_is_partition() calls will be replaced by
> rel->rd_partbound != NULL check. But one potential problem with that
> approach is that now whenever a parent relation is opened, all the
> partition relations must be opened to get the partbound value (to form the
> PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
> currently, we just look up the pg_partition catalog (or the associated
> cache) for every partition and that gets us the partbound.
Well, you could just look up the pg_class row without opening the
relation, too. There is a system cache on pg_class.oid, after all. I
think the issue is whether it's safe to read either one of those
things without a lock on the child relation. If altering the
partitioning information for a relation requires holding only
AccessExclusiveLock on that relation, and no lock on the parent, then
you really can't read the information for any child relation without
taking at least AccessShareLock. Otherwise, it might change under
you, and that would be bad.
I'm inclined to think that changing the partitioning information for a
child is going to require AccessExclusiveLock on both the child and
the parent. That seems unfortunate from a concurrency point of view,
but we may be stuck with it: suppose you require only
ShareUpdateExclusiveLock on the parent. Well, then a concurrent read
transaction might see the partition boundaries change when it does a
relcache rebuild, which would cause it to suddenly start expecting the
data to be in a different plan in mid-transaction, perhaps even in
mid-scan. Maybe that's survivable with really careful coding, but it
seems like it's probably a bad thing. For example, it would mean that
the executor would be unable to rely on the partitioning information
in the relcache remaining stable underneath it. Moreover, the
relcache is always going to be scanned with the most recent possible
MVCC snapshot, but the transaction snapshot may be older, so such a
system creates all sorts of nasty possibilities for there to be skew
between the snapshot being used to via the data and the snapshot being
used to read the metadata that says where the data is.
This may need some more thought, but if we go with that approach of
requiring an AccessExclusiveLock on both parent and child, then it
seems to me that maybe we should consider the partitioning information
to be a property of the parent rather than the child. Just take all
the partitioning information for all children and put it in one big
node tree and store it in the pg_class or pg_partition_root entry for
the parent as one big ol' varlena. Now you can open the parent and
get all of the partitioning information for all of the children
without needing any lock on any child, and that's *really* good,
because it means that some day we might be able to do partition
elimination before locking any of the children! That would be
excellent.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-22 04:51 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
4 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-08-22 04:51 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
The parent-child relationship of multi-level partitioned tables is not
retained when creating the AppendRelInfo nodes. We create RelOptInfo nodes
for all the leaf and intermediate tables. The AppendRelInfo nodes created
for these RelOptInfos set the topmost table as the parent of all the leaf
and child tables. Since partitioning scheme/s at each level is/are
associated with the parent/s at that level, we loose information about the
immediate parents and thus it becomes difficult to identify which leaf node
falls where in the partition hierarchy. This stops us from doing any
lump-sum partition pruning where we can eliminate all the partitions under
a given parent-partition if that parent-partition gets pruned. It also
restricts partition-wise join technique from being applied to partial
partition hierarchy when the whole partitioning scheme of joining tables
does not match. Maintaining a RelOptInfo hierarchy should not create
corresponding Append (all kinds) plan hierarchy since
accumulate_append_subpath() flattens any such hierarchy while creating
paths. Can you please consider this point in your upcoming patch?
On Wed, Aug 10, 2016 at 4:39 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
> 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/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXn
> a-urjm_UY9BqXj%3DEaDTSA%40mail.gmail.com
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-25 06:52 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-08-25 06:52 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: pgsql-hackers
On 2016/08/22 13:51, Ashutosh Bapat wrote:
> The parent-child relationship of multi-level partitioned tables is not
> retained when creating the AppendRelInfo nodes. We create RelOptInfo nodes
> for all the leaf and intermediate tables. The AppendRelInfo nodes created
> for these RelOptInfos set the topmost table as the parent of all the leaf
> and child tables. Since partitioning scheme/s at each level is/are
> associated with the parent/s at that level, we loose information about the
> immediate parents and thus it becomes difficult to identify which leaf node
> falls where in the partition hierarchy. This stops us from doing any
> lump-sum partition pruning where we can eliminate all the partitions under
> a given parent-partition if that parent-partition gets pruned. It also
> restricts partition-wise join technique from being applied to partial
> partition hierarchy when the whole partitioning scheme of joining tables
> does not match. Maintaining a RelOptInfo hierarchy should not create
> corresponding Append (all kinds) plan hierarchy since
> accumulate_append_subpath() flattens any such hierarchy while creating
> paths. Can you please consider this point in your upcoming patch?
I agree. So there seem to be two things here: a) when expanding a
partitioned table inheritance set, do it recursively such that resulting
AppendRelInfos preserve *immediate* parent-child relationship info. b)
when accumulating append subpaths, do not flatten a subpath that is itself
an append when ((AppendPath *) subpath)->path.parent is a RelOptInfo with
non-NULL partitioning info. Is the latter somehow necessary for
pairwise-join considerations?
I think I can manage to squeeze in (a) in the next version patch and will
also start working on (b), mainly the part about RelOptInfo getting some
partitioning info.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-25 07:15 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-08-25 07:15 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Thu, Aug 25, 2016 at 12:22 PM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/08/22 13:51, Ashutosh Bapat wrote:
> > The parent-child relationship of multi-level partitioned tables is not
> > retained when creating the AppendRelInfo nodes. We create RelOptInfo
> nodes
> > for all the leaf and intermediate tables. The AppendRelInfo nodes created
> > for these RelOptInfos set the topmost table as the parent of all the leaf
> > and child tables. Since partitioning scheme/s at each level is/are
> > associated with the parent/s at that level, we loose information about
> the
> > immediate parents and thus it becomes difficult to identify which leaf
> node
> > falls where in the partition hierarchy. This stops us from doing any
> > lump-sum partition pruning where we can eliminate all the partitions
> under
> > a given parent-partition if that parent-partition gets pruned. It also
> > restricts partition-wise join technique from being applied to partial
> > partition hierarchy when the whole partitioning scheme of joining tables
> > does not match. Maintaining a RelOptInfo hierarchy should not create
> > corresponding Append (all kinds) plan hierarchy since
> > accumulate_append_subpath() flattens any such hierarchy while creating
> > paths. Can you please consider this point in your upcoming patch?
>
> I agree. So there seem to be two things here: a) when expanding a
> partitioned table inheritance set, do it recursively such that resulting
> AppendRelInfos preserve *immediate* parent-child relationship info.
Right.
> b)
> when accumulating append subpaths, do not flatten a subpath that is itself
> an append when ((AppendPath *) subpath)->path.parent is a RelOptInfo with
> non-NULL partitioning info.Is the latter somehow necessary for
> pairwise-join considerations?
>
I don't think you need to do anything in the path creation code for this.
As is it flattens all AppendPath hierarchies whether for partitioning or
inheritance or subqueries. We should leave it as it is.
> I think I can manage to squeeze in (a) in the next version patch and will
> also start working on (b), mainly the part about RelOptInfo getting some
> partitioning info.
>
I am fine with b, where you would include some partitioning information in
RelOptInfo. But you don't need to do what you said in (b) above.
In a private conversation Robert Haas suggested a way slightly different
than what my patch for partition-wise join does. He suggested that the
partitioning schemes i.e strategy, number of partitions and bounds of the
partitioned elations involved in the query should be stored in PlannerInfo
in the form of a list. Each partitioning scheme is annotated with the
relids of the partitioned relations. RelOptInfo of the partitioned relation
will point to the partitioning scheme in PlannerInfo. Along-with that each
RelOptInfo will need to store partition keys for corresponding relation.
This simplifies matching the partitioning schemes of the joining relations.
Also it reduces the number of copies of partition bounds floating around as
we expect that a query will involve multiple partitioned tables following
similar partitioning schemes. May be you want to consider this idea while
working on (b).
> Thanks,
> Amit
>
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-26 08:03 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-08-26 08:03 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/08/18 5:23, Robert Haas wrote:
> On Wed, Aug 17, 2016 at 2:21 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> I am slightly tempted to eliminate the pg_partition catalog and associated
>> syscache altogether and add a column to pg_class as Robert suggested.
>> That way, all relid_is_partition() calls will be replaced by
>> rel->rd_partbound != NULL check. But one potential problem with that
>> approach is that now whenever a parent relation is opened, all the
>> partition relations must be opened to get the partbound value (to form the
>> PartitionDesc to be stored in parent relation's rd_partdesc). Whereas
>> currently, we just look up the pg_partition catalog (or the associated
>> cache) for every partition and that gets us the partbound.
>
> Well, you could just look up the pg_class row without opening the
> relation, too. There is a system cache on pg_class.oid, after all. I
Yes, I somehow didn't think of that.
> think the issue is whether it's safe to read either one of those
> things without a lock on the child relation. If altering the
> partitioning information for a relation requires holding only
> AccessExclusiveLock on that relation, and no lock on the parent, then
> you really can't read the information for any child relation without
> taking at least AccessShareLock. Otherwise, it might change under
> you, and that would be bad.
I'd imagine this won't be a problem because we take an AccessExclusiveLock
on the parent when adding/removing a partition.
> I'm inclined to think that changing the partitioning information for a
> child is going to require AccessExclusiveLock on both the child and
> the parent. That seems unfortunate from a concurrency point of view,
> but we may be stuck with it: suppose you require only
> ShareUpdateExclusiveLock on the parent. Well, then a concurrent read
> transaction might see the partition boundaries change when it does a
> relcache rebuild, which would cause it to suddenly start expecting the
> data to be in a different plan in mid-transaction, perhaps even in
> mid-scan. Maybe that's survivable with really careful coding, but it
> seems like it's probably a bad thing. For example, it would mean that
> the executor would be unable to rely on the partitioning information
> in the relcache remaining stable underneath it. Moreover, the
> relcache is always going to be scanned with the most recent possible
> MVCC snapshot, but the transaction snapshot may be older, so such a
> system creates all sorts of nasty possibilities for there to be skew
> between the snapshot being used to via the data and the snapshot being
> used to read the metadata that says where the data is.
We do take a lock on the parent because we would be changing its partition
descriptor (relcache). I changed MergeAttributes() such that an
AccessExclusiveLock instead of ShareUpdateExclusiveLock is taken if the
parent is a partitioned table.
> This may need some more thought, but if we go with that approach of
> requiring an AccessExclusiveLock on both parent and child, then it
> seems to me that maybe we should consider the partitioning information
> to be a property of the parent rather than the child. Just take all
> the partitioning information for all children and put it in one big
> node tree and store it in the pg_class or pg_partition_root entry for
> the parent as one big ol' varlena. Now you can open the parent and
> get all of the partitioning information for all of the children
> without needing any lock on any child, and that's *really* good,
> because it means that some day we might be able to do partition
> elimination before locking any of the children! That would be
> excellent.
If we need an AccessExclusiveLock on parent to add/remove a partition
(IOW, changing that child table's partitioning information), then do we
need to lock the individual partitions when reading partition's
information? I mean to ask why the simple syscache look-ups to get each
partition's bound wouldn't do.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-26 08:15 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-08-26 08:15 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
Sorry it took me a while to reply. Attached updated patches including the
review comments on 0001 at [1].
On 2016/08/17 3:54, Robert Haas wrote:
> On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> 0002-psql-and-pg_dump-support-for-partitioned-tables.patch
>
> + if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
>
> Version check is redundant, right?
Yep, fixed.
> +) 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)))
>
> I understand that it's probably difficult not to end up with two sets
> of parentheses here, but can we avoid ending up with three sets?
Fixed. This code was copy-pasted from the index code which has other
considerations for adding surrounding parentheses which don't apply to the
partition key code.
> Also, I wonder if pg_get_partkeydef() should omit "PARTITION BY" and
> pg_dump can add that part back. Then this could say:
>
> Partition Key: RANGE ((a + b))
>
> ...which seems a good deal more natural than what you have now.
Agreed, so done that way.
>> 0003-Catalog-and-DDL-for-partition-bounds.patch
>>
>> 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.
>
> Hmm, I don't think I like this. Why should it be necessary to detach
> a partition before dropping it? That seems like an unnecessary step.
I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.
> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY
> IMMEDIATE ]
> +
> </synopsis>
>
> Unnecessary hunk.
>
> + <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>
>
> Sentence fragment.
Fixed.
>
> + <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>
>
> This is another restriction I don't understand. Why can't I attach a
> partitioned table?
I removed this restriction.
ATExecAttachPartition() adds a AT work queue entry for the table being
attached to perform a heap scan in the rewrite phase. The scan is done
for checking that no row violates the partition boundary condition. When
attaching a partitioned table as partition, multiple AT work queue entries
are now added - one for each leaf partition of the table being attached.
> + 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.
>
> Ugh, why? I think this should work exactly the same way for
> partitioned tables that it does for any other inheritance hierarchy.
> Sure, you'll get no rows, but who cares?
Agreed, done that way.
> +CREATE FOREIGN TABLE measurement_y2016m07
> + PARTITION OF measurement FOR VALUES START ('2016-07-01') END
> ('2016-08-01');
> + SERVER server_07;
>
> Extra semicolon?
Fixed.
>
> + A partition cannot have columns other than those inherited from the
> + parent. That includes the <structfield>oid</> column, which can be
>
> I think experience suggests that this is a good restriction, but then
> why does the syntax synopsis indicate that PARTITION BY can be
> specified along with column definitions? Similarly for CREATE FOREIGN
> TABLE.
The syntax synopsis of CREATE TABLE ... PARTITION OF indicates that a list
of column WITH OPTION and/or table_constraint can be specified. It does
not allow column definitions.
In this case, inherited columns will be listed in the PARTITION BY clause.
Do you mean that the CREATE TABLE ... PARTITION OF syntax should allow
column definitions just like INHERITS does and unlike regular inheritance,
throw error if columns other than those to be merged are found?
>
> + When specifying for a table being created as partition, one needs to
> + use column names from the parent table as part of the key.
>
> This is not very clear.
Sentence removed because it may be clear from the context that inherited
columns are to be used for the partition key.
> - /* 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;
>
> It might be right to disallow NO INHERIT in this case, but I don't
> think it can be right to just silently ignore it.
OK, I changed this to instead throw an error if a NO INHERIT check
constraint is added to a partitioned table or a partition.
> + * Not flushed from the cache by RelationClearRelation() unless changed because
> + * of addition or removal of partitions.
>
> This seems unlikely to be safe, unless I'm missing something.
Like TupleDesc, a table's PartitionDesc is preserved across relcache
rebuilds. PartitionDesc consists of arrays of OIDs and partition bounds
for a table's immediate partitions. It can only change by adding/removing
partitions to/from the table which requires an exclusive lock on it.
Since this data can grow arbitrarily big it seemed better to not have to
copy it around, so a direct pointer to the relcache field (rd_partdesc) is
given to callers. relcache rebuilds that do not logically change
PartitionDesc leave it intact so that some user of it is not left with a
dangling pointer. Am I missing something?
>
> + form = (Form_pg_inherits) GETSTRUCT(tuple);
> +
> + systable_endscan(scan);
> + heap_close(catalogRelation, AccessShareLock);
> +
> + return form->inhparent;
>
> This is unsafe. After systable_endscan, it is no longer OK to access
> form->inhparent.
>
> Try building with CLOBBER_CACHE_ALWAYS to find other cache flush hazards.
>
> There should probably be a note in the function header comment that it
> is unsafe to use this for an inheritance child that is not a
> partition, because there could be more than one parent in that case.
> Or maybe the whole idea of this function just isn't very sound...
Fixed unsafe coding and added a comment to the function saying it should
be called on tables known to be partitions.
>
> +static List *
> +get_partitions(Oid relid, int lockmode)
> +{
> + return find_inheritance_children(relid, lockmode);
> +}
>
> What's the point? If we're going to have a wrapper here at all, then
> shouldn't it have a name that matches the existing convention - e.g.
> find_partitions() or find_child_partitions()? But I think you might
> as well just use find_inheritance_children() directly.
OK, changed to just use find_inheritance_children() directly.
>
> + * Happens when we have created the pg_inherits entry
> but not the
> + * pg_partition entry yet.
>
> Why do we ever allow the flow of control to reach this point while we
> are in such an intermediate state?
Fixed to prevent this from happening.
>
> +free_partition_info(PartitionInfoData **p, int num)
>
> Seems very error-prone. Isn't this why MemoryContextReset was invented?
Got rid of this function.
>
> +relid_is_partition(Oid relid)
> +{
> + return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
> +}
>
> This is used in a lot of places, and the overhead of checking it in
> all of those places is not necessarily nil. Syscache lookups aren't
> free. What if we didn't create a new catalog for this and instead
> just added a relpartitionbound attribute to pg_class? It seems a bit
> silly to have a whole extra catalog to store one extra column...
OK, I got rid of the pg_partition catalog and added a pg_class attribute
for storing partition bound. Because the newly added attribute is a
pg_node_tree and hence not readily accessible without a heap_getattr()
call, I added a boolean relispartition as well. Now all the
relid_is_partition() calls have been replaced by checks using
relation->rd_rel->relispartition.
>
> /*
> + * 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)));
> + }
>
> Why? This seems like an entirely arbitrary prohibition. If inserts
> aren't supported, then they'll fail at runtime. Same for updates or
> deletes, for which you have not added checks. I think you should just
> remove this.
OK, removed this check.
Instead, ExecInitModifyTable()/BeginCopy() call CheckValidResultRel() for
every leaf partition to check that they are ready for CMD_INSERT.
>
> + /* Force inheritance recursion, if partitioned table. */
> + if (recurse || relid_is_partitioned(myrelid))
>
> Disagree with this, too. There's no reason for partitioned tables to
> be special in this way. Similarly, disagree with all of the places
> that do something similar.
Removed the forced recursion bit here and a few other places.
>
> - 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")));
>
> You have a few of these; they cause problems for translators, because
> different languages have different word ordering. Repeat the entire
> message instead: is_attach_partition ? "column \"%s\" in source table
> must be marked NOT NULL" : "column \"%s\" in child table must be
> marked NOT NULL".
Changed so that the entire message is repeated.
>
> +-- XXX add ownership tests
>
> So do that. :-)
Done, sorry about that.
>
> +ERROR: column "b" is not null in parent
> +HINT: Please drop not null in the parent instead
>
> Hmm. That hint doesn't seem like project style, and I'm not sure
> that it really makes sense to issue such a hint anyway. Who knows
> whether that is the right thing to do? I think you should somehow be
> complaining about the fact that this is a partition, rather than
> complaining about the fact that the column is NOT NULL in the parent.
> Are we insisting that the flags match exactly, or only that the child
> may not allow nulls unless the parent does?
I think the latter. It is assumed that all the parent's constraints are
present in a child table, because in ExecInsert()/CopyFrom() we perform
ExecConstraints() using the child relation even if the actual insert was
on the parent. Also, if an inherited NOT NULL constraint on a child's
column is dropped irrespective of parent's, selecting a parent's NOT NULL
column might return nulls from the child table that no longer has the
constraint.
I recently came across a related proposal whereby dropping *inherited* NOT
NULL from child tables will be prevented. Problems in letting it be be
dropped are mentioned here:
https://www.postgresql.org/message-id/21633.1448383428@sss.pgh.pa.us
That proposal is probably being reworked such that NOT NULL constraints
get a pg_constraint entry with proper accounting of inheritance count.
> +ERROR: new partition's list of values overlaps with partition
> "lpart1" of "list_parted"
>
> Maybe just:
>
> ERROR: partitions must not overlap
> -or-
> ERROR: partition "%s" would overlap partition "%s"
OK, I changed to the second message.
>
> As before, this is just an initial read-through, so apologies for
> whatever I may have missed.
Thanks a lot for the review.
By the way, I am still working on the following items and will be included
in the next version of the patch.
* Fix internal representation of list partition bounds to be more efficient
* Add PartitionOptInfo
As mentioned in [2], I have refactored the inheritance expansion code
within optimizer so that a partitioned table's inheritance hierarchy is
preserved in resulting AppendRelInfos (patch 0005). One immediate benefit
of that is that if constraint exclusion determines that an intermediate
partition is to be excluded then all partitions underneath it are excluded
automatically. With the current flattened model, all partitions in that
subtree would have been processed and excluded one-by-one.
Regards,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoZ008qTgd_Qg6_oZb3i0mOYrS6MdhncwgcqPKahixjarg%40mail.g...
[2]
https://www.postgresql.org/message-id/f2a9592a-17e9-4c6a-e021-03b802195ce7%40lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-2.patch (115.9K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-2.patch)
download | inline diff:
From a5ba9cd74b09b1f5ea5833fb938a9b77ec7dab7d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.
2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 102 +++++++-
doc/src/sgml/ref/create_table.sgml | 55 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 6 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 2 +
src/backend/catalog/heap.c | 27 ++-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/partition.c | 396 +++++++++++++++++++++++++
src/backend/catalog/pg_depend.c | 3 +
src/backend/catalog/pg_partitioned_table.c | 172 +++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 7 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 384 +++++++++++++++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 33 ++
src/backend/nodes/equalfuncs.c | 28 ++
src/backend/nodes/outfuncs.c | 26 ++
src/backend/parser/gram.y | 110 ++++++--
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 69 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/cache/relcache.c | 19 ++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 8 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 35 +++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/catalog/pg_partitioned_table_fn.h | 29 ++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 9 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 155 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 133 +++++++++
54 files changed, 2035 insertions(+), 45 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/backend/catalog/pg_partitioned_table.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned_table.h
create mode 100644 src/include/catalog/pg_partitioned_table_fn.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4e09e06..84e3faf 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores information
+ about the partition key of tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <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 expression 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>
+ Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+ KEY constraints; however, you can define these constraints on individual
+ data partitions.
+ </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..09e29d1 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
- pg_type.o storage.o toasting.o
+ pg_type.o storage.o toasting.o pg_partitioned_table.o
BKIFILES = postgres.bki postgres.description postgres.shdescription
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a585c3a..679f1c7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..aafd2e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
+ /* Remove NO INHERIT flag if rel is a partitioned table */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
/*
* Create the Check Constraint
*/
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8068b82..5432b33 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..e8f1e94
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,396 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ char **partcolnames; /* partition key column names */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprbin_item;
+ List *partexprsrc;
+ ListCell *partexprsrc_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ /* Allocate in the supposedly short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprbin,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+
+ /* We should have a partexprsrc as well */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprsrc,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+ partexprsrc = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather partition column names (simple C strings) */
+ key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+ /* Copy partattrs and fill other per-attribute info */
+ partexprbin_item = list_head(key->partexprs);
+ partexprsrc_item = list_head(partexprsrc);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+
+ /* Collect atttribute names */
+ if (key->partattrs[i] != 0)
+ key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+ key->partattrs[i]);
+ else
+ {
+ Value *str = lfirst(partexprsrc_item);
+ key->partcolnames[i] = pstrdup(str->val.str);
+ partexprsrc_item = lnext(partexprsrc_item);
+ }
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+ return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int i;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc0(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc0(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+ newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+ for (i = 0; i < newkey->partnatts; i++)
+ newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..45ec347
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ * routines to support manipulation of the pg_partitioned_table relation
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_fn.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * StorePartitionKey
+ * Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprbinDatum;
+ Datum partexprsrcDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprbin)
+ {
+ char *exprbinString;
+ char *exprsrcString;
+
+ exprbinString = nodeToString(partexprbin);
+ exprsrcString = nodeToString(partexprsrc);
+ partexprbinDatum = CStringGetTextDatum(exprbinString);
+ partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+ pfree(exprbinString);
+ pfree(exprsrcString);
+ }
+ else
+ partexprbinDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprbinDatum)
+ {
+ nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+ nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+ }
+
+ values[Anum_pg_partitioned_table_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+ values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Store dependencies on anything mentioned in the key expressions.
+ * However, ignore the column references which causes self-dependencies
+ * to be created that are undesirable. That is done by asking the
+ * dependency-tracking sub-system to ignore any such dependencies.
+ */
+ if (partexprbin)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprbin,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9ac7122..bb67004 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -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_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f45b330..5120a4d 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_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..279cf99 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index bc2e4af..bd99fb6 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..e716032 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_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..3bb5933 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -433,6 +441,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass);
/* ----------------------------------------------------------------
@@ -596,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition key, if any */
+ if (stmt->partby)
+ {
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprbin = NIL;
+ List *partexprsrc = NIL;
+
+ stmt->partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(rel, stmt->partby->partParams,
+ partattrs, &partexprbin, &partexprsrc,
+ partopclass);
+
+ partnatts = list_length(stmt->partby->partParams);
+ StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+ partattrs, partexprbin, partexprsrc, partopclass);
+ }
+
/*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
@@ -955,7 +989,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+ * for OBJECT_TABLE relations. It is ok for the passed in relkind to be
+ * RELKIND_RELATION while the relation is actually a partitioned table.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1563,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4341,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4578,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_key_natts(key);
+ partexprs = get_partition_key_exprs(key);
+
+ partexpr_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partatt = get_partition_col_attnum(key, i);
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5705,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5749,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7906,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10067,6 +10231,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12066,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ char *exprsrc;
+
+ partattrs[attn] = 0; /* marks expression */
+ *partexprbin = lappend(*partexprbin, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ exprsrc = deparse_expression(expr,
+ deparse_context_for(RelationGetRelationName(rel),
+ RelationGetRelid(rel)),
+ false, false);
+ *partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+ }
+ }
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 99a659a..501bb93 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0563e63..6905498 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_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1877fb4..5668078 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3016,6 +3016,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);
@@ -4171,6 +4172,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
* ****************************************************************
@@ -5085,6 +5112,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 448e1a9..23b7d31 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2630,6 +2631,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
*/
@@ -3383,6 +3405,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 29b7712..8ad9781 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3279,6 +3280,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 -
@@ -3863,6 +3884,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 cb5cfc4..3d5cde9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,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.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13768,6 +13841,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..71c0c1c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e98fad0..819d403 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -759,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2517,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..41f9304 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..6d1d12b 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_TABLE
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8d2ad01..72fab9e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1053,6 +1055,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2050,6 +2061,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2991,7 +3004,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5045,6 +5060,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..fb540e1 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..ef50f85 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partedrelid_index, 3351, on pg_partitioned_table 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..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+extern int16 get_partition_col_attnum(PartitionKey key, int col);
+extern Oid get_partition_col_typid(PartitionKey key, int col);
+extern int32 get_partition_col_typmod(PartitionKey key, int col);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..9a0f6f4
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid 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 partexprbin; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+ pg_node_tree partexprsrc
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partedrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partexprbin 6
+#define Anum_pg_partitioned_table_partexprsrc 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ * prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 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 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 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..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..708232d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,158 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE fail_exclusion (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+ a int
+) PARTIION BY RANGE (a, a);
+ERROR: syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+ ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR: cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6e4a8be 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,136 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+ a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-2.patch (22.5K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-2.patch)
download | inline diff:
From 66f989f72bbdeec5ae799ff4a2c3abe76aaba2f5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 140 ++++++++++++++++++++++++++++
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, 260 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.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,144 @@ 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_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprbin))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprbin, &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);
+
+ 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 a5c2d09..c418ba7 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_TABLE);
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_TABLE)
+ 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_TABLE);
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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: 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 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-2.patch (196.6K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-2.patch)
download | inline diff:
From d970985a62d97c2ab81c920410abe6552a27feae Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 +++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/heap.c | 36 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1421 +++++++++++++++++++++++++++-
src/backend/catalog/pg_partitioned_table.c | 2 -
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 765 +++++++++++++---
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 | 331 +++++++-
src/backend/utils/cache/relcache.c | 82 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 25 +
src/include/catalog/pg_class.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/test/regress/expected/alter_table.out | 211 ++++
src/test/regress/expected/create_table.out | 188 ++++
src/test/regress/sql/alter_table.sql | 185 ++++
src/test/regress/sql/create_table.sql | 138 +++
35 files changed, 3964 insertions(+), 164 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 84e3faf..e7912b3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1229,6 +1311,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index aafd2e6..1613218 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"
@@ -90,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -770,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -808,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -818,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -851,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -924,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1033,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1268,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2055,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
- /* Remove NO INHERIT flag if rel is a partitioned table */
+ /* Remove NO INHERIT flag if rel is a partitioned table or a partition */
if (is_no_inherit &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2475,7 +2495,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e8f1e94..8093470 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* partition.c
- * Partitioning related utility functions.
+ * Partitioning related data structures and functions.
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
#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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
/* Type and collation information for partition key columns */
@@ -69,11 +77,107 @@ 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);
+/* Support RelationBuildPartitionDesc() */
+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
*/
@@ -394,3 +498,1318 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
return result;
}
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ PartitionInfo *parts;
+ MemoryContext oldcxt;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Collect partition info from the catalogs */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+ parts = (PartitionInfoData **)
+ palloc0(nparts * sizeof(PartitionInfoData *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData));
+ parts[i]->oid = partrelid;
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!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++;
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(rel->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;
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->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(char *relname, 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("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(with)),
+ 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("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(with)),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ heap_close(parent, AccessShareLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_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 (!rel->rd_rel->relispartition)
+ return NIL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* Nope, so generate. */
+ return generate_partition_check_qual(rel);
+}
+
+/* Module-local functions */
+
+/*
+ * 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
+ {
+ /*
+ * Determine operator inclusivity to use from the partition bound
+ * spec (lowerinc or upperinc).
+ */
+
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->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 partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_check_qual(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_check = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /*
+ * Generate executable expression using the information in
+ * ListPartitionSpec and RangePartitionSpec nodes.
+ */
+
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)), NoLock);
+ my_check = get_check_qual_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, add its expressions to the list.
+ */
+ if (parent->rd_rel->relispartition)
+ {
+ 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_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index 45ec347..5537ac2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
DEPENDENCY_IGNORE);
- /* Tell world about the key */
- CacheInvalidateRelcache(rel);
heap_close(pg_partitioned_table, RowExclusiveLock);
heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 43bbd90..9d80ed5 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -677,6 +677,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e716032..0805746 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 3bb5933..e45a83d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -164,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -280,7 +282,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);
@@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprbin, List **partexprsrc,
Oid *partopclass);
+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);
/* ----------------------------------------------------------------
@@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -589,10 +598,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
@@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
* for OBJECT_TABLE relations. It is ok for the passed in relkind to be
@@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1452,7 +1494,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;
@@ -1497,8 +1540,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),
@@ -1525,6 +1568,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),
@@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1709,6 +1783,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;
@@ -1837,6 +1912,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)
{
@@ -1866,16 +1944,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);
@@ -1912,8 +1994,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 */
@@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2581,11 +2669,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2827,8 +2916,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3107,6 +3199,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);
@@ -3424,6 +3521,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);
@@ -3743,6 +3846,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);
@@ -3928,7 +4037,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4008,6 +4117,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4072,6 +4182,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4261,6 +4380,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4458,7 +4583,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4780,6 +4906,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5302,6 +5433,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5832,6 +5983,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5924,9 +6080,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6315,8 +6473,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7877,7 +8037,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);
@@ -7936,8 +8098,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8009,6 +8173,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10176,6 +10345,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10188,12 +10362,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;
@@ -10238,37 +10407,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10303,6 +10446,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);
@@ -10317,16 +10523,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;
}
/*
@@ -10377,7 +10575,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
@@ -10395,12 +10593,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10422,14 +10624,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10440,8 +10646,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10456,7 +10664,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10470,7 +10680,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.
@@ -10489,10 +10699,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10539,7 +10753,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10547,7 +10763,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10558,6 +10776,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10571,7 +10790,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10582,6 +10803,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10595,13 +10856,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];
@@ -10610,19 +10869,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10632,7 +10882,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);
@@ -10653,11 +10903,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
@@ -10666,7 +10925,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)))
@@ -10728,7 +10987,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);
@@ -10759,7 +11018,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)
@@ -10771,30 +11030,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;
}
/*
@@ -12431,3 +12680,271 @@ ComputePartitionAttrs(Relation rel, 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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals,
+ *parent_quals = RelationGetPartitionCheckQual(rel);
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_check_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5668078..8dea2bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3016,6 +3017,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);
@@ -4198,6 +4200,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
* ****************************************************************
@@ -5118,6 +5157,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 23b7d31..40ad793 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2374,6 +2375,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);
@@ -2652,6 +2654,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
*/
@@ -3411,6 +3444,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 8ad9781..3b8fa6b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2586,6 +2587,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);
@@ -3301,6 +3303,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'
@@ -3890,6 +3911,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 6f9a81e..466c0f2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2264,6 +2264,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
@@ -2495,6 +2524,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 3d5cde9..b6cce2c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -601,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -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;
+ }
;
/*****************************************************************************
@@ -11135,6 +11334,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;
@@ -13734,6 +13934,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13780,6 +13981,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13822,6 +14024,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 71c0c1c..6d4645c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -364,6 +364,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 */
@@ -1732,6 +1752,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:
@@ -3364,6 +3385,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 819d403..563a41c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +239,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
@@ -249,11 +258,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")));
@@ -359,6 +371,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -898,6 +911,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1117,6 +1131,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;
@@ -2586,6 +2601,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2674,6 +2690,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;
@@ -3038,3 +3070,300 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_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_partition_col_name(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_partition_col_name(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_partition_col_name(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 72fab9e..b7404da 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,13 +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_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2063,6 +2101,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2211,11 +2253,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2226,6 +2269,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);
@@ -2256,6 +2300,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
@@ -2311,6 +2358,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 */
@@ -3537,6 +3591,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5062,6 +5130,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..b2782f8 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);
@@ -32,4 +45,16 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
extern int32 get_partition_col_typmod(PartitionKey key, int col);
extern char *get_partition_col_name(PartitionKey key, int col);
+/* 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(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+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_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_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 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..96bea6b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,214 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2fec847..2b12276 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -434,3 +434,191 @@ Table "public.describe_list_key"
Partition Key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_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 table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..bc955e0 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,188 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 4dd6a0a..c38312e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -415,3 +415,141 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-2.patch (17.8K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-2.patch)
download | inline diff:
From cebe7dd051eae89e007247aeae9650edde6fa99b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 77ce807..5d90413 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,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 c418ba7..626fa00 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6125,6 +6125,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15302,6 +15359,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 */
@@ -15330,8 +15398,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;
@@ -15360,7 +15431,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");
}
@@ -15425,15 +15497,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 (");
@@ -15603,6 +15682,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 10d924a..9554f5e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,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 2b12276..5162c82 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: 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 c38312e..b29ac79 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,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
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-2.patch (14.5K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-2.patch)
download | inline diff:
From 5d36974f4855fd7b6ddf0fe1e08f73aad7ac95e8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
src/backend/optimizer/prep/prepunion.c | 278 ++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
2 files changed, 204 insertions(+), 83 deletions(-)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..592214b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /* Do not flatten the inheritance hierarchy if partitioned table */
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+
+ /* Otherwise, OK to add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (childOID != parentOID)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
- */
- if (oldrc)
- {
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
- }
-
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-2.patch (17.2K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-2.patch)
download | inline diff:
From aab5a93200a8d24a10b9c351ac3199cd33e43da3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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 5120a4d..3f5c4e6 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 9773272..3d82658 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 5790edc..5b0e8cf 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 8ecc116..70e7d4f 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..89d5760 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 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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-2.patch (8.0K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-2.patch)
download | inline diff:
From 2226aaa01957e9805faa9d5dfe7afccb3b7fdae8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 206 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 211 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8093470..9499afb 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,
@@ -149,6 +204,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);
@@ -896,6 +955,53 @@ 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_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 */
/*
@@ -1495,6 +1601,106 @@ 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;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 b2782f8..85384cb 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);
@@ -57,4 +58,8 @@ extern List *get_leaf_partition_oids(Oid relid, int lockmode);
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_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-2.patch (40.3K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-2.patch)
download | inline diff:
From 320fd42db6bb2f6a122ce95a0d1cc0bd93b5c721 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 348 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 46 ++++-
src/backend/executor/nodeModifyTable.c | 123 +++++++++++
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, 907 insertions(+), 9 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 9499afb..59f2127 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -208,6 +208,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);
@@ -236,6 +248,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
@@ -1617,7 +1633,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;
@@ -1701,6 +1717,270 @@ 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)
+{
+ Relation partRel;
+ 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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->parts[index]->oid, NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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 */
/*
@@ -2019,3 +2299,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 3f5c4e6..4963cb8 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,94 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +1753,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1672,6 +1768,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 +2329,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 +2350,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2459,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2487,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 +2509,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 +2600,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 +2645,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 +2684,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 +2705,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 +2724,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 e45a83d..5b4cec9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1236,6 +1236,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 3d82658..2f22b3f 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 5b0e8cf..cb47035 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,79 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +2063,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 47158f6..32f4031 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_table_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6152,6 +6153,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 (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ 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 70e7d4f..6ef45d5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1708,3 +1708,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..9f87f57 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_table_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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 85384cb..14fd29e 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);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-2.patch (24.7K, ../../169708f6-6e5a-18d1-707b-1b323e4a6baf@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-2.patch)
download | inline diff:
From 8b6f7276c5db879c1bf619952c7a1518048873d4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 a393813..253eeb4 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-29 11:53 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-08-29 11:53 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Aug 26, 2016 at 1:33 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> We do take a lock on the parent because we would be changing its partition
> descriptor (relcache). I changed MergeAttributes() such that an
> AccessExclusiveLock instead of ShareUpdateExclusiveLock is taken if the
> parent is a partitioned table.
Hmm, that seems both good and bad. On the good side, as mentioned,
being able to rely on the partition descriptor not changing under us
makes this sort of thing much easier to reason about. On the bad
side, it isn't good for this feature to have worse concurrency than
regular inheritance. Not sure what to do about this.
> If we need an AccessExclusiveLock on parent to add/remove a partition
> (IOW, changing that child table's partitioning information), then do we
> need to lock the individual partitions when reading partition's
> information? I mean to ask why the simple syscache look-ups to get each
> partition's bound wouldn't do.
Well, if X can't be changed without having an AccessExclusiveLock on
the parent, then an AccessShareLock on the parent is sufficient to
read X, right? Because those lock modes conflict.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-31 07:07 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-08-31 07:07 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/08/29 20:53, Robert Haas wrote:
> On Fri, Aug 26, 2016 at 1:33 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> We do take a lock on the parent because we would be changing its partition
>> descriptor (relcache). I changed MergeAttributes() such that an
>> AccessExclusiveLock instead of ShareUpdateExclusiveLock is taken if the
>> parent is a partitioned table.
>
> Hmm, that seems both good and bad. On the good side, as mentioned,
> being able to rely on the partition descriptor not changing under us
> makes this sort of thing much easier to reason about. On the bad
> side, it isn't good for this feature to have worse concurrency than
> regular inheritance. Not sure what to do about this.
>
>> If we need an AccessExclusiveLock on parent to add/remove a partition
>> (IOW, changing that child table's partitioning information), then do we
>> need to lock the individual partitions when reading partition's
>> information? I mean to ask why the simple syscache look-ups to get each
>> partition's bound wouldn't do.
>
> Well, if X can't be changed without having an AccessExclusiveLock on
> the parent, then an AccessShareLock on the parent is sufficient to
> read X, right? Because those lock modes conflict.
Yes. And hence we can proceed with performing partition elimination
before locking any of children. Lock on parent (AccessShareLock) will
prevent any of existing partitions to be removed and any new partitions to
be added because those operations require AccessExclusiveLock on the
parent. What I was trying to understand is why this would not be possible
with a design where partition bound is stored in the catalog as a property
of individual partitions instead of a design where we store collection of
partition bounds as a property of the parent.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-31 07:17 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-08-31 07:17 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Aug 31, 2016 at 12:37 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> If we need an AccessExclusiveLock on parent to add/remove a partition
>>> (IOW, changing that child table's partitioning information), then do we
>>> need to lock the individual partitions when reading partition's
>>> information? I mean to ask why the simple syscache look-ups to get each
>>> partition's bound wouldn't do.
>>
>> Well, if X can't be changed without having an AccessExclusiveLock on
>> the parent, then an AccessShareLock on the parent is sufficient to
>> read X, right? Because those lock modes conflict.
>
> Yes. And hence we can proceed with performing partition elimination
> before locking any of children. Lock on parent (AccessShareLock) will
> prevent any of existing partitions to be removed and any new partitions to
> be added because those operations require AccessExclusiveLock on the
> parent.
Agreed.
> What I was trying to understand is why this would not be possible
> with a design where partition bound is stored in the catalog as a property
> of individual partitions instead of a design where we store collection of
> partition bounds as a property of the parent.
From the point of view of feasibility, I don't think it matters very
much where the property is stored; it's the locking that is the key
thing. In other words, I think this *would* be possible if the
partition bound is stored as a property of individual partitions, as
long as it can't change without a lock on the parent.
However, it seems a lot better to make it a property of the parent
from a performance point of view. Suppose there are 1000 partitions.
Reading one toasted value for pg_class and running stringToNode() on
it is probably a lot faster than scanning pg_inherits to find all of
the child partitions and then doing an index scan to find the pg_class
tuple for each and then decoding all of those tuples and assembling
them into some data structure.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-08-31 07:35 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-08-31 07:35 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/08/31 16:17, Robert Haas wrote:
> On Wed, Aug 31, 2016 at 12:37 PM, Amit Langote wrote:
>> What I was trying to understand is why this would not be possible
>> with a design where partition bound is stored in the catalog as a property
>> of individual partitions instead of a design where we store collection of
>> partition bounds as a property of the parent.
>
> From the point of view of feasibility, I don't think it matters very
> much where the property is stored; it's the locking that is the key
> thing. In other words, I think this *would* be possible if the
> partition bound is stored as a property of individual partitions, as
> long as it can't change without a lock on the parent.
>
> However, it seems a lot better to make it a property of the parent
> from a performance point of view. Suppose there are 1000 partitions.
> Reading one toasted value for pg_class and running stringToNode() on
> it is probably a lot faster than scanning pg_inherits to find all of
> the child partitions and then doing an index scan to find the pg_class
> tuple for each and then decoding all of those tuples and assembling
> them into some data structure.
Seems worth trying. One point that bothers me a bit is how do we enforce
partition bound condition on individual partition basis. For example when
a row is inserted into a partition directly, we better check that it does
not fall outside the bounds and issue an error otherwise. With current
approach, we just look up a partition's bound from the catalog and gin up
a check constraint expression (and cache in relcache) to be enforced in
ExecConstraints(). With the new approach, I guess we would need to look
up the parent's partition descriptor. Note that the checking in
ExecConstraints() is turned off when routing a tuple from the parent.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-01 05:17 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-01 05:17 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: pgsql-hackers
On 2016/08/25 16:15, Ashutosh Bapat wrote:
> On Thu, Aug 25, 2016 at 12:22 PM, Amit Langote wrote:
>> b)
>> when accumulating append subpaths, do not flatten a subpath that is itself
>> an append when ((AppendPath *) subpath)->path.parent is a RelOptInfo with
>> non-NULL partitioning info.Is the latter somehow necessary for
>> pairwise-join considerations?
>
> I don't think you need to do anything in the path creation code for this.
> As is it flattens all AppendPath hierarchies whether for partitioning or
> inheritance or subqueries. We should leave it as it is.
I thought it would be convenient for pairwise join code to work with the
hierarchy intact even within the AppendPath tree. If it turns out to be
so, maybe that patch can take care of it.
>> I think I can manage to squeeze in (a) in the next version patch and will
>> also start working on (b), mainly the part about RelOptInfo getting some
>> partitioning info.
>
> I am fine with b, where you would include some partitioning information in
> RelOptInfo. But you don't need to do what you said in (b) above.
>
> In a private conversation Robert Haas suggested a way slightly different
> than what my patch for partition-wise join does. He suggested that the
> partitioning schemes i.e strategy, number of partitions and bounds of the
> partitioned elations involved in the query should be stored in PlannerInfo
> in the form of a list. Each partitioning scheme is annotated with the
> relids of the partitioned relations. RelOptInfo of the partitioned relation
> will point to the partitioning scheme in PlannerInfo. Along-with that each
> RelOptInfo will need to store partition keys for corresponding relation.
> This simplifies matching the partitioning schemes of the joining relations.
> Also it reduces the number of copies of partition bounds floating around as
> we expect that a query will involve multiple partitioned tables following
> similar partitioning schemes. May be you want to consider this idea while
> working on (b).
So IIUC, a partitioned relation's (baserel or joinrel) RelOptInfo has only
the information about partition keys. They will be matched with query
restriction quals pruning away any unneeded partitions which happens
individually for each such parent baserel (within set_append_rel_size() I
suppose). Further, two joining relations are eligible to be considered
for pairwise joining if they have identical partition keys and query
equi-join quals match the same. The resulting joinrel will have the same
partition key (as either joining relation) and will have as many
partitions as there are in the intersection of sets of partitions of
joining rels (intersection proceeds by matching partition bounds).
"Partition scheme" structs go into a PlannerInfo list member, one
corresponding to each partitioned relation - baserel or joinrel, right?
As you say, each such struct has the following pieces of information:
strategy, num_partitions, bounds (and other auxiliary info). Before
make_one_rel() starts, the list has one for each partitioned baserel.
After make_one_rel() has formed baserel pathlists and before
make_rel_from_joinlist() is called, are the partition scheme structs of
processed baserels marked with some information about the pruning activity
that occurred so far? Then as we build successively higher levels of
joinrels, new entries will be made for those joinrels for which we added
pairwise join paths, with relids matching the corresponding joinrels.
Does that make sense?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-01 08:17 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-01 08:17 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
>
> > I don't think you need to do anything in the path creation code for this.
> > As is it flattens all AppendPath hierarchies whether for partitioning or
> > inheritance or subqueries. We should leave it as it is.
>
> I thought it would be convenient for pairwise join code to work with the
> hierarchy intact even within the AppendPath tree. If it turns out to be
> so, maybe that patch can take care of it.
>
Partition-wise join work with RelOptInfos, so it's fine if the AppendPath
hierarchy is flattened out. We need the RelOptInfo hierarchy though.
>
> >> I think I can manage to squeeze in (a) in the next version patch and
> will
> >> also start working on (b), mainly the part about RelOptInfo getting some
> >> partitioning info.
> >
> > I am fine with b, where you would include some partitioning information
> in
> > RelOptInfo. But you don't need to do what you said in (b) above.
> >
> > In a private conversation Robert Haas suggested a way slightly different
> > than what my patch for partition-wise join does. He suggested that the
> > partitioning schemes i.e strategy, number of partitions and bounds of the
> > partitioned elations involved in the query should be stored in
> PlannerInfo
> > in the form of a list. Each partitioning scheme is annotated with the
> > relids of the partitioned relations. RelOptInfo of the partitioned
> relation
> > will point to the partitioning scheme in PlannerInfo. Along-with that
> each
> > RelOptInfo will need to store partition keys for corresponding relation.
> > This simplifies matching the partitioning schemes of the joining
> relations.
> > Also it reduces the number of copies of partition bounds floating around
> as
> > we expect that a query will involve multiple partitioned tables following
> > similar partitioning schemes. May be you want to consider this idea while
> > working on (b).
>
> So IIUC, a partitioned relation's (baserel or joinrel) RelOptInfo has only
> the information about partition keys. They will be matched with query
> restriction quals pruning away any unneeded partitions which happens
> individually for each such parent baserel (within set_append_rel_size() I
> suppose). Further, two joining relations are eligible to be considered
> for pairwise joining if they have identical partition keys and query
> equi-join quals match the same. The resulting joinrel will have the same
> partition key (as either joining relation) and will have as many
> partitions as there are in the intersection of sets of partitions of
> joining rels (intersection proceeds by matching partition bounds).
>
> "Partition scheme" structs go into a PlannerInfo list member, one
> corresponding to each partitioned relation - baserel or joinrel, right?
>
Multiple relations (base or join) can share Partition Scheme if they are
partitioned the same way. Each partition scheme also stores the relids of
the base relations partitioned by that scheme.
> As you say, each such struct has the following pieces of information:
> strategy, num_partitions, bounds (and other auxiliary info). Before
> make_one_rel() starts, the list has one for each partitioned baserel.
>
After make_one_rel() has formed baserel pathlists and before
> make_rel_from_joinlist() is called, are the partition scheme structs of
> processed baserels marked with some information about the pruning activity
> that occurred so far?
Right now pruned partitions are labelled as dummy rels (empty appent
paths). That's enough to detect a pruned partition. I haven't found a need
to label partitioning scheme with pruned partitions for partition-wise join.
> Then as we build successively higher levels of
> joinrels, new entries will be made for those joinrels for which we added
> pairwise join paths, with relids matching the corresponding joinrels.
> Does that make sense?
>
>
I don't think we will make any new partition scheme entry in PlannerInfo
after all the base relations have been considered. Partitionin-wise join
will pick the one suitable for the given join. But in case partition-wise
join needs to make new entries, I will take care of that in my patch.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-02 05:38 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
4 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-02 05:38 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
Here's something I observed with your set of patches posted in June. I have
not checked the latest set of patches. So, if it's something fixed, please
ignore the mail and sorry for me being lazy.
prt1 is partitioned table and it shows following information with \d+
regression=# \d+ prt1
Partitioned table "public.prt1"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Key: PARTITION BY RANGE (a)
Indexes:
"iprt1_a" btree (a)
Shouldn't we show all the partitions of this table and may be their ranges
of lists?
I found the partitions from EXPLAIN plan
regression=# explain verbose select * from prt1;
QUERY PLAN
-----------------------------------------------------------------------
Append (cost=0.00..6.00 rows=301 width=13)
-> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
Output: prt1.a, prt1.b, prt1.c
-> Seq Scan on public.prt1_p1 (cost=0.00..2.25 rows=125 width=13)
Output: prt1_p1.a, prt1_p1.b, prt1_p1.c
-> Seq Scan on public.prt1_p3 (cost=0.00..1.50 rows=50 width=13)
Output: prt1_p3.a, prt1_p3.b, prt1_p3.c
-> Seq Scan on public.prt1_p2 (cost=0.00..2.25 rows=125 width=13)
Output: prt1_p2.a, prt1_p2.b, prt1_p2.c
(9 rows)
Then did \d+ on each of those to find their ranges
regression=# \d+ prt1_p1
Table "public.prt1_p1"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Of: prt1 FOR VALUES START (0) END (250)
Indexes:
"iprt1_p1_a" btree (a)
regression=# \d+ prt1_p2
Table "public.prt1_p2"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Of: prt1 FOR VALUES START (250) END (500)
Indexes:
"iprt1_p2_a" btree (a)
regression=# \d+ prt1_p3
Table "public.prt1_p3"
Column | Type | Modifiers | Storage | Stats target |
Description
--------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | integer | | plain | |
c | character varying | | extended | |
Partition Of: prt1 FOR VALUES START (500) END (600)
Indexes:
"iprt1_p3_a" btree (a)
As you will observe that the table prt1 can not have any row with a < 0 and
a > 600. But when I execute
regression=# explain verbose select * from prt1 where a > 1000000;
QUERY PLAN
------------------------------------------------------------------
Append (cost=0.00..0.00 rows=1 width=40)
-> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
Output: prt1.a, prt1.b, prt1.c
Filter: (prt1.a > 1000000)
(4 rows)
it correctly excluded all the partitions, but did not exclude the parent
relation. I guess, we have enough information to exclude it. Probably, we
should add a check constraint on the parent which is OR of the check
constraints on all the partitions. So there are two problems here
1. \d+ doesn't show partitions - this is probably reported earlier, I don't
remember.
2. A combination of constraints on the partitions should be applicable to
the parent. We aren't doing that.
On Wed, Aug 10, 2016 at 4:39 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
> 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/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXn
> a-urjm_UY9BqXj%3DEaDTSA%40mail.gmail.com
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-02 06:08 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-02 06:08 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: pgsql-hackers
On 2016/09/02 14:38, Ashutosh Bapat wrote:
> Here's something I observed with your set of patches posted in June. I have
> not checked the latest set of patches. So, if it's something fixed, please
> ignore the mail and sorry for me being lazy.
>
> prt1 is partitioned table and it shows following information with \d+
>
> regression=# \d+ prt1
> Partitioned table "public.prt1"
> Column | Type | Modifiers | Storage | Stats target |
> Description
> --------+-------------------+-----------+----------+--------------+-------------
> a | integer | | plain | |
> b | integer | | plain | |
> c | character varying | | extended | |
> Partition Key: PARTITION BY RANGE (a)
> Indexes:
> "iprt1_a" btree (a)
>
> Shouldn't we show all the partitions of this table and may be their ranges
> of lists?
Something I thought about as well. I will implement that.
> I found the partitions from EXPLAIN plan
>
> regression=# explain verbose select * from prt1;
> QUERY PLAN
> -----------------------------------------------------------------------
> Append (cost=0.00..6.00 rows=301 width=13)
> -> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
> Output: prt1.a, prt1.b, prt1.c
> -> Seq Scan on public.prt1_p1 (cost=0.00..2.25 rows=125 width=13)
> Output: prt1_p1.a, prt1_p1.b, prt1_p1.c
> -> Seq Scan on public.prt1_p3 (cost=0.00..1.50 rows=50 width=13)
> Output: prt1_p3.a, prt1_p3.b, prt1_p3.c
> -> Seq Scan on public.prt1_p2 (cost=0.00..2.25 rows=125 width=13)
> Output: prt1_p2.a, prt1_p2.b, prt1_p2.c
> (9 rows)
>
> Then did \d+ on each of those to find their ranges
[ ... ]
>
> As you will observe that the table prt1 can not have any row with a < 0 and
> a > 600. But when I execute
>
> regression=# explain verbose select * from prt1 where a > 1000000;
> QUERY PLAN
> ------------------------------------------------------------------
> Append (cost=0.00..0.00 rows=1 width=40)
> -> Seq Scan on public.prt1 (cost=0.00..0.00 rows=1 width=40)
> Output: prt1.a, prt1.b, prt1.c
> Filter: (prt1.a > 1000000)
> (4 rows)
>
> it correctly excluded all the partitions, but did not exclude the parent
> relation. I guess, we have enough information to exclude it. Probably, we
> should add a check constraint on the parent which is OR of the check
> constraints on all the partitions. So there are two problems here
>
> 1. \d+ doesn't show partitions - this is probably reported earlier, I don't
> remember.
You just did, :)
As I said I will implement that on lines of how inheritance children are
listed (with additional information ie, range or list).
> 2. A combination of constraints on the partitions should be applicable to
> the parent. We aren't doing that.
How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
table, we can have get_relation_constraints() include a constant false
clause in the list of constraints returned for
relation_excluded_by_constraints() to process so that it is not included
in the append result by way of constraint exclusion. One more option is
to mark such rels dummy in set_rel_size().
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-02 06:22 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-02 06:22 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
>
>
> > 2. A combination of constraints on the partitions should be applicable to
> > the parent. We aren't doing that.
>
> How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
> table, we can have get_relation_constraints() include a constant false
> clause in the list of constraints returned for
> relation_excluded_by_constraints() to process so that it is not included
> in the append result by way of constraint exclusion. One more option is
> to mark such rels dummy in set_rel_size().
>
>
I am not complaining about having parent relation there. For the people who
are used to seeing the parent relation in the list of append relations, it
may be awkward. But +1 if we can do that. If we can't do that, we should at
least mark with an OR of all constraints on the partitions, so that
constraint exclusion can exclude it if there are conditions incompatible
with constraints. This is what would happen in inheritance case as well, if
there are constraints on the parent. In the above example, the parent table
would have constraints CHECK ((a >= 0 AND a < 250) OR (a >= 250 and a <
500) OR (a >= 500 or a < 600)). It will probably get excluded, if
constraint exclusion is smart enough to understand ORing.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-02 06:53 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-02 06:53 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: pgsql-hackers
On 2016/09/02 15:22, Ashutosh Bapat wrote:
>>
>>
>>> 2. A combination of constraints on the partitions should be applicable to
>>> the parent. We aren't doing that.
>>
>> How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
>> table, we can have get_relation_constraints() include a constant false
>> clause in the list of constraints returned for
>> relation_excluded_by_constraints() to process so that it is not included
>> in the append result by way of constraint exclusion. One more option is
>> to mark such rels dummy in set_rel_size().
>>
>>
> I am not complaining about having parent relation there. For the people who
> are used to seeing the parent relation in the list of append relations, it
> may be awkward. But +1 if we can do that. If we can't do that, we should at
> least mark with an OR of all constraints on the partitions, so that
> constraint exclusion can exclude it if there are conditions incompatible
> with constraints. This is what would happen in inheritance case as well, if
> there are constraints on the parent. In the above example, the parent table
> would have constraints CHECK ((a >= 0 AND a < 250) OR (a >= 250 and a <
> 500) OR (a >= 500 or a < 600)). It will probably get excluded, if
> constraint exclusion is smart enough to understand ORing.
I guess constraint exclusion would be (is) smart enough to handle that
correctly but why make it unnecessarily spend a *significant* amount of
time on doing the proof (when we *know* we can just skip it). Imagine how
long the OR list could get. By the way, my suggestion of just returning a
constant false clause also does not work - neither in case of a query with
restrictions (predicate has to be an OpExpr to go ahead with the proof
which constant false clause is not) nor in case of a query without
restrictions (proof is not performed at all). So, that one is useless.
Getting rid of the parent table in the append list by other means may be a
way to go. We know that the table is empty and safe to just drop.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-02 06:57 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-02 06:57 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Fri, Sep 2, 2016 at 12:23 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
> On 2016/09/02 15:22, Ashutosh Bapat wrote:
> >>
> >>
> >>> 2. A combination of constraints on the partitions should be applicable
> to
> >>> the parent. We aren't doing that.
> >>
> >> How about on seeing that a RELOPT_OTHER_MEMBER_REL is partitioned parent
> >> table, we can have get_relation_constraints() include a constant false
> >> clause in the list of constraints returned for
> >> relation_excluded_by_constraints() to process so that it is not
> included
> >> in the append result by way of constraint exclusion. One more option is
> >> to mark such rels dummy in set_rel_size().
> >>
> >>
> > I am not complaining about having parent relation there. For the people
> who
> > are used to seeing the parent relation in the list of append relations,
> it
> > may be awkward. But +1 if we can do that. If we can't do that, we should
> at
> > least mark with an OR of all constraints on the partitions, so that
> > constraint exclusion can exclude it if there are conditions incompatible
> > with constraints. This is what would happen in inheritance case as well,
> if
> > there are constraints on the parent. In the above example, the parent
> table
> > would have constraints CHECK ((a >= 0 AND a < 250) OR (a >= 250 and a <
> > 500) OR (a >= 500 or a < 600)). It will probably get excluded, if
> > constraint exclusion is smart enough to understand ORing.
>
> I guess constraint exclusion would be (is) smart enough to handle that
> correctly but why make it unnecessarily spend a *significant* amount of
> time on doing the proof (when we *know* we can just skip it). Imagine how
> long the OR list could get. By the way, my suggestion of just returning a
> constant false clause also does not work - neither in case of a query with
> restrictions (predicate has to be an OpExpr to go ahead with the proof
> which constant false clause is not) nor in case of a query without
> restrictions (proof is not performed at all). So, that one is useless.
>
Huh!
>
> Getting rid of the parent table in the append list by other means may be a
> way to go. We know that the table is empty and safe to just drop.
>
>
Ok. Does a constraint (1 = 2) or something like that which is obviously
false, help?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-02 07:07 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-09-02 07:07 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: pgsql-hackers
On 2016/09/02 15:57, Ashutosh Bapat wrote:
> On Fri, Sep 2, 2016 at 12:23 PM, Amit Langote wrote:
>> Getting rid of the parent table in the append list by other means may be a
>> way to go. We know that the table is empty and safe to just drop.
>>
> Ok. Does a constraint (1 = 2) or something like that which is obviously
> false, help?
No, anything like that would get reduced to a constant false clause by
eval_const_expressions() processing.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 07:13 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-06 07:13 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
I found a server crash while running make check in regress folder. with
this set of patches. Problem is RelationBuildPartitionKey() partexprsrc may
be used uninitialized. Initializing it with NIL fixes the crash. Here's
patch to fix it. Came up with the fix after discussion with Amit.
On Fri, Aug 26, 2016 at 1:45 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Sorry it took me a while to reply. Attached updated patches including the
> review comments on 0001 at [1].
>
> On 2016/08/17 3:54, Robert Haas wrote:
> > On Wed, Aug 10, 2016 at 7:09 AM, Amit Langote
> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> >> 0002-psql-and-pg_dump-support-for-partitioned-tables.patch
> >
> > + if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
> >
> > Version check is redundant, right?
>
> Yep, fixed.
>
> > +) 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)))
> >
> > I understand that it's probably difficult not to end up with two sets
> > of parentheses here, but can we avoid ending up with three sets?
>
> Fixed. This code was copy-pasted from the index code which has other
> considerations for adding surrounding parentheses which don't apply to the
> partition key code.
>
> > Also, I wonder if pg_get_partkeydef() should omit "PARTITION BY" and
> > pg_dump can add that part back. Then this could say:
> >
> > Partition Key: RANGE ((a + b))
> >
> > ...which seems a good deal more natural than what you have now.
>
> Agreed, so done that way.
>
> >> 0003-Catalog-and-DDL-for-partition-bounds.patch
> >>
> >> 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.
> >
> > Hmm, I don't think I like this. Why should it be necessary to detach
> > a partition before dropping it? That seems like an unnecessary step.
>
> I thought we had better lock the parent table when removing one of its
> partitions and it seemed a bit odd to lock the parent table when dropping
> a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
> PARTITION, the parent table is locked anyway.
>
> > [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY
> > IMMEDIATE ]
> > +
> > </synopsis>
> >
> > Unnecessary hunk.
> >
> > + <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>
> >
> > Sentence fragment.
>
> Fixed.
>
> >
> > + <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>
> >
> > This is another restriction I don't understand. Why can't I attach a
> > partitioned table?
>
> I removed this restriction.
>
> ATExecAttachPartition() adds a AT work queue entry for the table being
> attached to perform a heap scan in the rewrite phase. The scan is done
> for checking that no row violates the partition boundary condition. When
> attaching a partitioned table as partition, multiple AT work queue entries
> are now added - one for each leaf partition of the table being attached.
>
> > + 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.
> >
> > Ugh, why? I think this should work exactly the same way for
> > partitioned tables that it does for any other inheritance hierarchy.
> > Sure, you'll get no rows, but who cares?
>
> Agreed, done that way.
>
> > +CREATE FOREIGN TABLE measurement_y2016m07
> > + PARTITION OF measurement FOR VALUES START ('2016-07-01') END
> > ('2016-08-01');
> > + SERVER server_07;
> >
> > Extra semicolon?
>
> Fixed.
>
> >
> > + A partition cannot have columns other than those inherited from
> the
> > + parent. That includes the <structfield>oid</> column, which can
> be
> >
> > I think experience suggests that this is a good restriction, but then
> > why does the syntax synopsis indicate that PARTITION BY can be
> > specified along with column definitions? Similarly for CREATE FOREIGN
> > TABLE.
>
> The syntax synopsis of CREATE TABLE ... PARTITION OF indicates that a list
> of column WITH OPTION and/or table_constraint can be specified. It does
> not allow column definitions.
>
> In this case, inherited columns will be listed in the PARTITION BY clause.
>
> Do you mean that the CREATE TABLE ... PARTITION OF syntax should allow
> column definitions just like INHERITS does and unlike regular inheritance,
> throw error if columns other than those to be merged are found?
>
> >
> > + When specifying for a table being created as partition, one needs
> to
> > + use column names from the parent table as part of the key.
> >
> > This is not very clear.
>
> Sentence removed because it may be clear from the context that inherited
> columns are to be used for the partition key.
>
> > - /* 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;
> >
> > It might be right to disallow NO INHERIT in this case, but I don't
> > think it can be right to just silently ignore it.
>
> OK, I changed this to instead throw an error if a NO INHERIT check
> constraint is added to a partitioned table or a partition.
>
> > + * Not flushed from the cache by RelationClearRelation() unless changed
> because
> > + * of addition or removal of partitions.
> >
> > This seems unlikely to be safe, unless I'm missing something.
>
> Like TupleDesc, a table's PartitionDesc is preserved across relcache
> rebuilds. PartitionDesc consists of arrays of OIDs and partition bounds
> for a table's immediate partitions. It can only change by adding/removing
> partitions to/from the table which requires an exclusive lock on it.
> Since this data can grow arbitrarily big it seemed better to not have to
> copy it around, so a direct pointer to the relcache field (rd_partdesc) is
> given to callers. relcache rebuilds that do not logically change
> PartitionDesc leave it intact so that some user of it is not left with a
> dangling pointer. Am I missing something?
>
> >
> > + form = (Form_pg_inherits) GETSTRUCT(tuple);
> > +
> > + systable_endscan(scan);
> > + heap_close(catalogRelation, AccessShareLock);
> > +
> > + return form->inhparent;
> >
> > This is unsafe. After systable_endscan, it is no longer OK to access
> > form->inhparent.
> >
> > Try building with CLOBBER_CACHE_ALWAYS to find other cache flush hazards.
> >
> > There should probably be a note in the function header comment that it
> > is unsafe to use this for an inheritance child that is not a
> > partition, because there could be more than one parent in that case.
> > Or maybe the whole idea of this function just isn't very sound...
>
> Fixed unsafe coding and added a comment to the function saying it should
> be called on tables known to be partitions.
>
> >
> > +static List *
> > +get_partitions(Oid relid, int lockmode)
> > +{
> > + return find_inheritance_children(relid, lockmode);
> > +}
> >
> > What's the point? If we're going to have a wrapper here at all, then
> > shouldn't it have a name that matches the existing convention - e.g.
> > find_partitions() or find_child_partitions()? But I think you might
> > as well just use find_inheritance_children() directly.
>
> OK, changed to just use find_inheritance_children() directly.
>
> >
> > + * Happens when we have created the pg_inherits entry
> > but not the
> > + * pg_partition entry yet.
> >
> > Why do we ever allow the flow of control to reach this point while we
> > are in such an intermediate state?
>
> Fixed to prevent this from happening.
>
> >
> > +free_partition_info(PartitionInfoData **p, int num)
> >
> > Seems very error-prone. Isn't this why MemoryContextReset was invented?
>
> Got rid of this function.
>
> >
> > +relid_is_partition(Oid relid)
> > +{
> > + return SearchSysCacheExists1(PARTRELID,
> ObjectIdGetDatum(relid));
> > +}
> >
> > This is used in a lot of places, and the overhead of checking it in
> > all of those places is not necessarily nil. Syscache lookups aren't
> > free. What if we didn't create a new catalog for this and instead
> > just added a relpartitionbound attribute to pg_class? It seems a bit
> > silly to have a whole extra catalog to store one extra column...
>
> OK, I got rid of the pg_partition catalog and added a pg_class attribute
> for storing partition bound. Because the newly added attribute is a
> pg_node_tree and hence not readily accessible without a heap_getattr()
> call, I added a boolean relispartition as well. Now all the
> relid_is_partition() calls have been replaced by checks using
> relation->rd_rel->relispartition.
>
> >
> > /*
> > + * 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)));
> > + }
> >
> > Why? This seems like an entirely arbitrary prohibition. If inserts
> > aren't supported, then they'll fail at runtime. Same for updates or
> > deletes, for which you have not added checks. I think you should just
> > remove this.
>
> OK, removed this check.
>
> Instead, ExecInitModifyTable()/BeginCopy() call CheckValidResultRel() for
> every leaf partition to check that they are ready for CMD_INSERT.
>
> >
> > + /* Force inheritance recursion, if partitioned table. */
> > + if (recurse || relid_is_partitioned(myrelid))
> >
> > Disagree with this, too. There's no reason for partitioned tables to
> > be special in this way. Similarly, disagree with all of the places
> > that do something similar.
>
> Removed the forced recursion bit here and a few other places.
>
> >
> > - 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")));
> >
> > You have a few of these; they cause problems for translators, because
> > different languages have different word ordering. Repeat the entire
> > message instead: is_attach_partition ? "column \"%s\" in source table
> > must be marked NOT NULL" : "column \"%s\" in child table must be
> > marked NOT NULL".
>
> Changed so that the entire message is repeated.
>
> >
> > +-- XXX add ownership tests
> >
> > So do that. :-)
>
> Done, sorry about that.
>
> >
> > +ERROR: column "b" is not null in parent
> > +HINT: Please drop not null in the parent instead
> >
> > Hmm. That hint doesn't seem like project style, and I'm not sure
> > that it really makes sense to issue such a hint anyway. Who knows
> > whether that is the right thing to do? I think you should somehow be
> > complaining about the fact that this is a partition, rather than
> > complaining about the fact that the column is NOT NULL in the parent.
> > Are we insisting that the flags match exactly, or only that the child
> > may not allow nulls unless the parent does?
>
> I think the latter. It is assumed that all the parent's constraints are
> present in a child table, because in ExecInsert()/CopyFrom() we perform
> ExecConstraints() using the child relation even if the actual insert was
> on the parent. Also, if an inherited NOT NULL constraint on a child's
> column is dropped irrespective of parent's, selecting a parent's NOT NULL
> column might return nulls from the child table that no longer has the
> constraint.
>
> I recently came across a related proposal whereby dropping *inherited* NOT
> NULL from child tables will be prevented. Problems in letting it be be
> dropped are mentioned here:
>
> https://www.postgresql.org/message-id/21633.1448383428@sss.pgh.pa.us
>
> That proposal is probably being reworked such that NOT NULL constraints
> get a pg_constraint entry with proper accounting of inheritance count.
>
> > +ERROR: new partition's list of values overlaps with partition
> > "lpart1" of "list_parted"
> >
> > Maybe just:
> >
> > ERROR: partitions must not overlap
> > -or-
> > ERROR: partition "%s" would overlap partition "%s"
>
> OK, I changed to the second message.
>
> >
> > As before, this is just an initial read-through, so apologies for
> > whatever I may have missed.
>
> Thanks a lot for the review.
>
> By the way, I am still working on the following items and will be included
> in the next version of the patch.
>
> * Fix internal representation of list partition bounds to be more efficient
> * Add PartitionOptInfo
>
> As mentioned in [2], I have refactored the inheritance expansion code
> within optimizer so that a partitioned table's inheritance hierarchy is
> preserved in resulting AppendRelInfos (patch 0005). One immediate benefit
> of that is that if constraint exclusion determines that an intermediate
> partition is to be excluded then all partitions underneath it are excluded
> automatically. With the current flattened model, all partitions in that
> subtree would have been processed and excluded one-by-one.
>
> Regards,
> Amit
>
> [1]
> https://www.postgresql.org/message-id/CA%2BTgmoZ008qTgd_Qg6_
> oZb3i0mOYrS6MdhncwgcqPKahixjarg%40mail.gmail.com
> [2]
> https://www.postgresql.org/message-id/f2a9592a-17e9-4c6a-
> e021-03b802195ce7%40lab.ntt.co.jp
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] fix_server_crash.patch (469B, ../../CAFjFpRcR=8J0kSx6kHVWGtun1k+Jfb3JfV0R038ns0a_287HAg@mail.gmail.com/3-fix_server_crash.patch)
download | inline diff:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 59f2127..f17ac29 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -281,7 +281,7 @@ RelationBuildPartitionKey(Relation relation)
oidvector *opclass;
KeyTypeCollInfo *tcinfo;
ListCell *partexprbin_item;
- List *partexprsrc;
+ List *partexprsrc = NIL;
ListCell *partexprsrc_item;
Datum datum;
MemoryContext partkeycxt,
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 08:07 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-06 08:07 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi,
On 2016/09/06 16:13, Ashutosh Bapat wrote:
> I found a server crash while running make check in regress folder. with
> this set of patches. Problem is RelationBuildPartitionKey() partexprsrc may
> be used uninitialized. Initializing it with NIL fixes the crash. Here's
> patch to fix it. Came up with the fix after discussion with Amit.
Thanks for the report. Here is a rebased version of the patches including
you fix (no significant changes from those posted on Aug 26).
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-3.patch (115.9K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-3.patch)
download | inline diff:
From 2d3c5684761bfd58c7d16f16f89bd93c0afa8bb9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.
2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 102 +++++++-
doc/src/sgml/ref/create_table.sgml | 55 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 6 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 2 +
src/backend/catalog/heap.c | 27 ++-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/partition.c | 394 +++++++++++++++++++++++++
src/backend/catalog/pg_depend.c | 3 +
src/backend/catalog/pg_partitioned_table.c | 172 +++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 7 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 384 ++++++++++++++++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 33 ++
src/backend/nodes/equalfuncs.c | 28 ++
src/backend/nodes/outfuncs.c | 26 ++
src/backend/parser/gram.y | 110 ++++++--
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 69 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/cache/relcache.c | 19 ++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 8 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 35 +++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/catalog/pg_partitioned_table_fn.h | 29 ++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 9 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 155 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 133 +++++++++
54 files changed, 2033 insertions(+), 45 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/backend/catalog/pg_partitioned_table.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned_table.h
create mode 100644 src/include/catalog/pg_partitioned_table_fn.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..0b38ff7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores information
+ about the partition key of tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <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 expression 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>
+ Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+ KEY constraints; however, you can define these constraints on individual
+ data partitions.
+ </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..09e29d1 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
- pg_type.o storage.o toasting.o
+ pg_type.o storage.o toasting.o pg_partitioned_table.o
BKIFILES = postgres.bki postgres.description postgres.shdescription
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a585c3a..679f1c7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..aafd2e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
+ /* Remove NO INHERIT flag if rel is a partitioned table */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
/*
* Create the Check Constraint
*/
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9aa8174..e0d56a9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..35e020c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ char **partcolnames; /* partition key column names */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprbin_item;
+ List *partexprsrc = NIL;
+ ListCell *partexprsrc_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ /* Allocate in the supposedly short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprbin,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+
+ /* We should have a partexprsrc as well */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprsrc,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+ partexprsrc = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather partition column names (simple C strings) */
+ key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+ /* Copy partattrs and fill other per-attribute info */
+ partexprbin_item = list_head(key->partexprs);
+ partexprsrc_item = list_head(partexprsrc);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+
+ /* Collect atttribute names */
+ if (key->partattrs[i] != 0)
+ key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+ key->partattrs[i]);
+ else
+ {
+ Value *str = lfirst(partexprsrc_item);
+ key->partcolnames[i] = pstrdup(str->val.str);
+ partexprsrc_item = lnext(partexprsrc_item);
+ }
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+ return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int i;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc0(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc0(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+ newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+ for (i = 0; i < newkey->partnatts; i++)
+ newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..45ec347
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ * routines to support manipulation of the pg_partitioned_table relation
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_fn.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * StorePartitionKey
+ * Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprbinDatum;
+ Datum partexprsrcDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprbin)
+ {
+ char *exprbinString;
+ char *exprsrcString;
+
+ exprbinString = nodeToString(partexprbin);
+ exprsrcString = nodeToString(partexprsrc);
+ partexprbinDatum = CStringGetTextDatum(exprbinString);
+ partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+ pfree(exprbinString);
+ pfree(exprsrcString);
+ }
+ else
+ partexprbinDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprbinDatum)
+ {
+ nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+ nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+ }
+
+ values[Anum_pg_partitioned_table_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+ values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Store dependencies on anything mentioned in the key expressions.
+ * However, ignore the column references which causes self-dependencies
+ * to be created that are undesirable. That is done by asking the
+ * dependency-tracking sub-system to ignore any such dependencies.
+ */
+ if (partexprbin)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprbin,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 5947e72..be71334 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1716,6 +1716,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_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..e716032 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_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..3bb5933 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -433,6 +441,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass);
/* ----------------------------------------------------------------
@@ -596,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition key, if any */
+ if (stmt->partby)
+ {
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprbin = NIL;
+ List *partexprsrc = NIL;
+
+ stmt->partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(rel, stmt->partby->partParams,
+ partattrs, &partexprbin, &partexprsrc,
+ partopclass);
+
+ partnatts = list_length(stmt->partby->partParams);
+ StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+ partattrs, partexprbin, partexprsrc, partopclass);
+ }
+
/*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
@@ -955,7 +989,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+ * for OBJECT_TABLE relations. It is ok for the passed in relkind to be
+ * RELKIND_RELATION while the relation is actually a partitioned table.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1563,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4341,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4578,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_key_natts(key);
+ partexprs = get_partition_key_exprs(key);
+
+ partexpr_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partatt = get_partition_col_attnum(key, i);
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5705,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5749,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7906,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10067,6 +10231,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12066,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ char *exprsrc;
+
+ partattrs[attn] = 0; /* marks expression */
+ *partexprbin = lappend(*partexprbin, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ exprsrc = deparse_expression(expr,
+ deparse_context_for(RelationGetRelationName(rel),
+ RelationGetRelid(rel)),
+ false, false);
+ *partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+ }
+ }
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1877fb4..5668078 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3016,6 +3016,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);
@@ -4171,6 +4172,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
* ****************************************************************
@@ -5085,6 +5112,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 448e1a9..23b7d31 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2630,6 +2631,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
*/
@@ -3383,6 +3405,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 29b7712..8ad9781 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3279,6 +3280,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 -
@@ -3863,6 +3884,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 cb5cfc4..3d5cde9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,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.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13768,6 +13841,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..71c0c1c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e98fad0..819d403 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -759,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2517,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..41f9304 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac64135..71d48df 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_TABLE
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8cbd6e7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1050,6 +1052,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2053,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5050,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..fb540e1 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..ef50f85 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partedrelid_index, 3351, on pg_partitioned_table 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..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+extern int16 get_partition_col_attnum(PartitionKey key, int col);
+extern Oid get_partition_col_typid(PartitionKey key, int col);
+extern int32 get_partition_col_typmod(PartitionKey key, int col);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..9a0f6f4
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid 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 partexprbin; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+ pg_node_tree partexprsrc
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partedrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partexprbin 6
+#define Anum_pg_partitioned_table_partexprsrc 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ * prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 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 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 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..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..708232d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,158 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE fail_exclusion (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+ a int
+) PARTIION BY RANGE (a, a);
+ERROR: syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+ ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR: cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6e4a8be 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,136 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+ a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-3.patch (22.5K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-3.patch)
download | inline diff:
From ed52e9fef8fc5176e3fe26c9a6a12ff98b39802b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 140 ++++++++++++++++++++++++++++
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, 260 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.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,144 @@ 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_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprbin))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprbin, &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);
+
+ 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 a5c2d09..c418ba7 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_TABLE);
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_TABLE)
+ 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_TABLE);
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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: 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 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-3.patch (197.0K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-3.patch)
download | inline diff:
From 24cfacb3c2fbcf4e081f611e5ea41323e00f35ec Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 +++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/heap.c | 36 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1419 +++++++++++++++++++++++++++-
src/backend/catalog/pg_partitioned_table.c | 2 -
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 773 +++++++++++++---
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 | 331 +++++++-
src/backend/utils/cache/relcache.c | 82 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 25 +
src/include/catalog/pg_class.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/test/regress/expected/alter_table.out | 211 ++++
src/test/regress/expected/create_table.out | 188 ++++
src/test/regress/sql/alter_table.sql | 185 ++++
src/test/regress/sql/create_table.sql | 138 +++
35 files changed, 3969 insertions(+), 165 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b38ff7..444e9fe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1229,6 +1311,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index aafd2e6..1613218 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"
@@ -90,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -770,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -808,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -818,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -851,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -924,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1033,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1268,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2055,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
- /* Remove NO INHERIT flag if rel is a partitioned table */
+ /* Remove NO INHERIT flag if rel is a partitioned table or a partition */
if (is_no_inherit &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2475,7 +2495,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 35e020c..6d8759c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* partition.c
- * Partitioning related utility functions.
+ * Partitioning related data structures and functions.
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
#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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
/* Type and collation information for partition key columns */
@@ -69,11 +77,107 @@ 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);
+/* Support RelationBuildPartitionDesc() */
+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
*/
@@ -392,3 +496,1316 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
return result;
}
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ PartitionInfo *parts;
+ MemoryContext oldcxt;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Collect partition info from the catalogs */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+ parts = (PartitionInfoData **)
+ palloc0(nparts * sizeof(PartitionInfoData *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData));
+ parts[i]->oid = partrelid;
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!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++;
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (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;
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->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(char *relname, 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("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(with)),
+ 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("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(with)),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ heap_close(parent, AccessShareLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_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 (!rel->rd_rel->relispartition)
+ return NIL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* Nope, so generate. */
+ return generate_partition_check_qual(rel);
+}
+
+/* Module-local functions */
+
+/*
+ * 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
+ {
+ /*
+ * Determine operator inclusivity to use from the partition bound
+ * spec (lowerinc or upperinc).
+ */
+
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->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 partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_check_qual(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_check = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /*
+ * Generate executable expression using the information in
+ * ListPartitionSpec and RangePartitionSpec nodes.
+ */
+
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)), NoLock);
+ my_check = get_check_qual_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, add its expressions to the list.
+ */
+ if (parent->rd_rel->relispartition)
+ {
+ 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_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index 45ec347..5537ac2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
DEPENDENCY_IGNORE);
- /* Tell world about the key */
- CacheInvalidateRelcache(rel);
heap_close(pg_partitioned_table, RowExclusiveLock);
heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e716032..0805746 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 3bb5933..3cc45ef 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -164,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -280,7 +282,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);
@@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprbin, List **partexprsrc,
Oid *partopclass);
+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);
/* ----------------------------------------------------------------
@@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -589,10 +598,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
@@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
* for OBJECT_TABLE relations. It is ok for the passed in relkind to be
@@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1452,7 +1494,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;
@@ -1497,8 +1540,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),
@@ -1525,6 +1568,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),
@@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1709,6 +1783,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;
@@ -1837,6 +1912,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)
{
@@ -1866,16 +1944,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);
@@ -1912,8 +1994,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 */
@@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2581,11 +2669,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2827,8 +2916,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3107,6 +3199,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);
@@ -3424,6 +3521,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);
@@ -3494,7 +3597,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3743,6 +3852,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);
@@ -3928,7 +4043,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4008,6 +4123,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4072,6 +4188,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4261,6 +4386,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4458,7 +4589,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4780,6 +4912,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5302,6 +5439,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5832,6 +5989,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5924,9 +6086,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6315,8 +6479,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7877,7 +8043,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);
@@ -7936,8 +8104,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8009,6 +8179,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10176,6 +10351,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10188,12 +10368,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;
@@ -10238,37 +10413,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10303,6 +10452,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);
@@ -10317,16 +10529,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;
}
/*
@@ -10377,7 +10581,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
@@ -10395,12 +10599,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10422,14 +10630,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10440,8 +10652,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10456,7 +10670,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10470,7 +10686,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.
@@ -10489,10 +10705,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10539,7 +10759,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10547,7 +10769,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10558,6 +10782,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10571,7 +10796,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10582,6 +10809,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10595,13 +10862,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];
@@ -10610,19 +10875,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10632,7 +10888,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);
@@ -10653,11 +10909,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
@@ -10666,7 +10931,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)))
@@ -10728,7 +10993,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);
@@ -10759,7 +11024,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)
@@ -10771,30 +11036,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;
}
/*
@@ -12431,3 +12686,271 @@ ComputePartitionAttrs(Relation rel, 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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals,
+ *parent_quals = RelationGetPartitionCheckQual(rel);
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_check_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5668078..8dea2bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3016,6 +3017,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);
@@ -4198,6 +4200,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
* ****************************************************************
@@ -5118,6 +5157,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 23b7d31..40ad793 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2374,6 +2375,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);
@@ -2652,6 +2654,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
*/
@@ -3411,6 +3444,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 8ad9781..3b8fa6b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2586,6 +2587,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);
@@ -3301,6 +3303,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'
@@ -3890,6 +3911,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 6f9a81e..466c0f2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2264,6 +2264,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
@@ -2495,6 +2524,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 3d5cde9..b6cce2c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -601,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -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;
+ }
;
/*****************************************************************************
@@ -11135,6 +11334,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;
@@ -13734,6 +13934,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13780,6 +13981,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13822,6 +14024,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 71c0c1c..6d4645c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -364,6 +364,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 */
@@ -1732,6 +1752,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:
@@ -3364,6 +3385,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 819d403..563a41c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +239,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
@@ -249,11 +258,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")));
@@ -359,6 +371,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -898,6 +911,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1117,6 +1131,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;
@@ -2586,6 +2601,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2674,6 +2690,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;
@@ -3038,3 +3070,300 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_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_partition_col_name(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_partition_col_name(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_partition_col_name(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 8cbd6e7..ac9ec92 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);
/*
@@ -925,6 +927,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
@@ -1052,13 +1085,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2055,6 +2093,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2203,11 +2245,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2218,6 +2261,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);
@@ -2248,6 +2292,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
@@ -2303,6 +2350,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 */
@@ -3529,6 +3583,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5052,6 +5120,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..b2782f8 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);
@@ -32,4 +45,16 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
extern int32 get_partition_col_typmod(PartitionKey key, int col);
extern char *get_partition_col_name(PartitionKey key, int col);
+/* 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(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+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_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_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 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..96bea6b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,214 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2fec847..2b12276 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -434,3 +434,191 @@ Table "public.describe_list_key"
Partition Key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_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 table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..bc955e0 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,188 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 4dd6a0a..c38312e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -415,3 +415,141 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-3.patch (17.8K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-3.patch)
download | inline diff:
From 6739e22a37e3066447125205ae01a7e2957c78cf Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 77ce807..5d90413 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,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 c418ba7..626fa00 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6125,6 +6125,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15302,6 +15359,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 */
@@ -15330,8 +15398,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;
@@ -15360,7 +15431,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");
}
@@ -15425,15 +15497,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 (");
@@ -15603,6 +15682,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 10d924a..9554f5e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,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 2b12276..5162c82 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: 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 c38312e..b29ac79 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,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
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-3.patch (14.5K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-3.patch)
download | inline diff:
From 7543ccfdae5505916cbe508a59d322606b7c78c4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
src/backend/optimizer/prep/prepunion.c | 278 ++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
2 files changed, 204 insertions(+), 83 deletions(-)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..592214b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /* Do not flatten the inheritance hierarchy if partitioned table */
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+
+ /* Otherwise, OK to add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (childOID != parentOID)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
- */
- if (oldrc)
- {
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
- }
-
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-3.patch (17.2K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-3.patch)
download | inline diff:
From f633b3e397db99cc887f6b017856d3c6bc555b43 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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 be71334..ba5bf23 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2460,7 +2460,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 9773272..3d82658 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 5790edc..5b0e8cf 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 8ecc116..70e7d4f 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 e28477d..e35da66 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..89d5760 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 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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-3.patch (8.0K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-3.patch)
download | inline diff:
From 3ed8417779e5bad7d9d83223d23471f07b87b001 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 206 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 211 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6d8759c..97491ec 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,
@@ -149,6 +204,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);
@@ -892,6 +951,53 @@ 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_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 */
/*
@@ -1491,6 +1597,106 @@ 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;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 b2782f8..85384cb 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);
@@ -57,4 +58,8 @@ extern List *get_leaf_partition_oids(Oid relid, int lockmode);
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_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-3.patch (40.3K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-3.patch)
download | inline diff:
From ab847366e344c3f713807758a25ffcb194ea6a09 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 348 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 46 ++++-
src/backend/executor/nodeModifyTable.c | 123 +++++++++++
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, 907 insertions(+), 9 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 97491ec..38cbc6f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -208,6 +208,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);
@@ -236,6 +248,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
@@ -1613,7 +1629,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;
@@ -1697,6 +1713,270 @@ 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)
+{
+ Relation partRel;
+ 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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->parts[index]->oid, NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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 */
/*
@@ -2015,3 +2295,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 ba5bf23..6fe9b0f 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.
@@ -1362,6 +1368,94 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1657,6 +1751,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1670,6 +1766,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);
}
@@ -2212,6 +2325,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2232,7 +2346,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2340,6 +2455,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2367,6 +2483,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;
@@ -2388,10 +2505,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 */
@@ -2443,6 +2596,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 */
@@ -2463,7 +2641,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)
@@ -2493,7 +2680,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),
@@ -2513,6 +2701,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2526,7 +2720,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 3cc45ef..e5ee8f7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1236,6 +1236,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 3d82658..2f22b3f 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 5b0e8cf..cb47035 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,79 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +2063,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 47158f6..32f4031 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_table_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6152,6 +6153,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 (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ 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 70e7d4f..6ef45d5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1708,3 +1708,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..9f87f57 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_table_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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 85384cb..14fd29e 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);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 e35da66..39ca517 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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-3.patch (24.7K, ../../4f7108cb-7185-e956-d746-ad756b52431a@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-3.patch)
download | inline diff:
From 523358f44e4166c30ad05e6f0bf8ed1642ea4300 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 a393813..253eeb4 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 11:15 Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Rajkumar Raghuwanshi @ 2016-09-06 11:15 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi,
I have applied updated patches given by you, and observe below.
here in the given example, t6_p3 partition is not allowed to have null, but
I am able to insert it, causing two nulls in the table.
--create a partition table
create table t6 (a int, b varchar) partition by list(a);
create table t6_p1 partition of t6 for values in (1,2,null);
create table t6_p2 partition of t6 for values in (4,5);
create table t6_p3 partition of t6 for values in (3,6);
--insert some values
insert into t6 select i,i::varchar from generate_series(1,6) i;
insert into t6 values (null,'A');
--try inserting null to t6_p3 partition table
insert into t6_p3 values (null,'A');
select tableoid::regclass,* from t6;
tableoid | a | b
----------+---+---
t6_p1 | 1 | 1
t6_p1 | 2 | 2
t6_p1 | | A
t6_p2 | 4 | 4
t6_p2 | 5 | 5
t6_p3 | 3 | 3
t6_p3 | 6 | 6
t6_p3 | | A
(8 rows)
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
On Tue, Sep 6, 2016 at 1:37 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
>
> Hi,
>
> On 2016/09/06 16:13, Ashutosh Bapat wrote:
> > I found a server crash while running make check in regress folder. with
> > this set of patches. Problem is RelationBuildPartitionKey() partexprsrc
> may
> > be used uninitialized. Initializing it with NIL fixes the crash. Here's
> > patch to fix it. Came up with the fix after discussion with Amit.
>
> Thanks for the report. Here is a rebased version of the patches including
> you fix (no significant changes from those posted on Aug 26).
>
> Thanks,
> Amit
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 12:19 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-09-06 12:19 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Aug 31, 2016 at 1:05 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> However, it seems a lot better to make it a property of the parent
>> from a performance point of view. Suppose there are 1000 partitions.
>> Reading one toasted value for pg_class and running stringToNode() on
>> it is probably a lot faster than scanning pg_inherits to find all of
>> the child partitions and then doing an index scan to find the pg_class
>> tuple for each and then decoding all of those tuples and assembling
>> them into some data structure.
>
> Seems worth trying. One point that bothers me a bit is how do we enforce
> partition bound condition on individual partition basis. For example when
> a row is inserted into a partition directly, we better check that it does
> not fall outside the bounds and issue an error otherwise. With current
> approach, we just look up a partition's bound from the catalog and gin up
> a check constraint expression (and cache in relcache) to be enforced in
> ExecConstraints(). With the new approach, I guess we would need to look
> up the parent's partition descriptor. Note that the checking in
> ExecConstraints() is turned off when routing a tuple from the parent.
[ Sorry for the slow response. ]
Yeah, that's a problem. Maybe it's best to associate this data with
the childrels after all - or halfway in between, e.g. augment
pg_inherits with this information. After all, the performance problem
I was worried about above isn't really much of an issue: each backend
will build a relcache entry for the parent just once and then use it
for the lifetime of the session unless some invalidation occurs. So
if that takes a small amount of extra time, it's probably not really a
big deal. On the other hand, if we can't build the implicit
constraint for the child table without opening the parent, that's
probably going to cause us some serious inconvenience.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 12:24 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-06 12:24 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
This patch uses RangeBound structure. There's also a structure defined with
the same name in rangetypes.h with some slight differences. Should we
rename the one in partition.c as PartRangeBound or something like that to
avoid the confusion?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 13:04 Amit Langote <amitlangote09@gmail.com>
parent: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-06 13:04 UTC (permalink / raw)
To: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi,
On Tue, Sep 6, 2016 at 8:15 PM, Rajkumar Raghuwanshi
<rajkumar.raghuwanshi@enterprisedb.com> wrote:
> Hi,
>
> I have applied updated patches given by you, and observe below.
>
> here in the given example, t6_p3 partition is not allowed to have null, but
> I am able to insert it, causing two nulls in the table.
>
> --create a partition table
> create table t6 (a int, b varchar) partition by list(a);
> create table t6_p1 partition of t6 for values in (1,2,null);
> create table t6_p2 partition of t6 for values in (4,5);
> create table t6_p3 partition of t6 for values in (3,6);
>
> --insert some values
> insert into t6 select i,i::varchar from generate_series(1,6) i;
> insert into t6 values (null,'A');
>
> --try inserting null to t6_p3 partition table
> insert into t6_p3 values (null,'A');
>
> select tableoid::regclass,* from t6;
> tableoid | a | b
> ----------+---+---
> t6_p1 | 1 | 1
> t6_p1 | 2 | 2
> t6_p1 | | A
> t6_p2 | 4 | 4
> t6_p2 | 5 | 5
> t6_p3 | 3 | 3
> t6_p3 | 6 | 6
> t6_p3 | | A
> (8 rows)
Thanks for testing!
That looks like a bug. The same won't occur if you had inserted
through the parent (it would be correctly routed to the partition that
has been defined to accept nulls (that is, t6_p1 of your example). The
bug seems to have to do with the handling of nulls in generating
implicit constraints from the list of values of a given list
partition. The direct insert on t6_p1 should have failed because
partition key has a value (null) violating the partition boundary
condition. Will fix.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-06 13:07 Amit Langote <amitlangote09@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-06 13:07 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Sep 6, 2016 at 9:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Wed, Aug 31, 2016 at 1:05 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> However, it seems a lot better to make it a property of the parent
>>> from a performance point of view. Suppose there are 1000 partitions.
>>> Reading one toasted value for pg_class and running stringToNode() on
>>> it is probably a lot faster than scanning pg_inherits to find all of
>>> the child partitions and then doing an index scan to find the pg_class
>>> tuple for each and then decoding all of those tuples and assembling
>>> them into some data structure.
>>
>> Seems worth trying. One point that bothers me a bit is how do we enforce
>> partition bound condition on individual partition basis. For example when
>> a row is inserted into a partition directly, we better check that it does
>> not fall outside the bounds and issue an error otherwise. With current
>> approach, we just look up a partition's bound from the catalog and gin up
>> a check constraint expression (and cache in relcache) to be enforced in
>> ExecConstraints(). With the new approach, I guess we would need to look
>> up the parent's partition descriptor. Note that the checking in
>> ExecConstraints() is turned off when routing a tuple from the parent.
>
> [ Sorry for the slow response. ]
>
> Yeah, that's a problem. Maybe it's best to associate this data with
> the childrels after all - or halfway in between, e.g. augment
> pg_inherits with this information. After all, the performance problem
> I was worried about above isn't really much of an issue: each backend
> will build a relcache entry for the parent just once and then use it
> for the lifetime of the session unless some invalidation occurs. So
> if that takes a small amount of extra time, it's probably not really a
> big deal. On the other hand, if we can't build the implicit
> constraint for the child table without opening the parent, that's
> probably going to cause us some serious inconvenience.
Agreed. So I will stick with the existing approach.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-07 08:56 Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Rajkumar Raghuwanshi @ 2016-09-07 08:56 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Hi,
I have a query regarding list partitioning,
For example if I want to store employee data in a table, with "IT" dept
employee in emp_p1 partition, "HR" dept employee in emp_p2 partition and if
employee belongs to other than these two, should come in emp_p3 partition.
In this case not sure how to create partition table. Do we have something
like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
list partition.
create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (??);
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
On Tue, Sep 6, 2016 at 6:37 PM, Amit Langote <amitlangote09@gmail.com>
wrote:
> On Tue, Sep 6, 2016 at 9:19 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> > On Wed, Aug 31, 2016 at 1:05 PM, Amit Langote
> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> >>> However, it seems a lot better to make it a property of the parent
> >>> from a performance point of view. Suppose there are 1000 partitions.
> >>> Reading one toasted value for pg_class and running stringToNode() on
> >>> it is probably a lot faster than scanning pg_inherits to find all of
> >>> the child partitions and then doing an index scan to find the pg_class
> >>> tuple for each and then decoding all of those tuples and assembling
> >>> them into some data structure.
> >>
> >> Seems worth trying. One point that bothers me a bit is how do we
> enforce
> >> partition bound condition on individual partition basis. For example
> when
> >> a row is inserted into a partition directly, we better check that it
> does
> >> not fall outside the bounds and issue an error otherwise. With current
> >> approach, we just look up a partition's bound from the catalog and gin
> up
> >> a check constraint expression (and cache in relcache) to be enforced in
> >> ExecConstraints(). With the new approach, I guess we would need to look
> >> up the parent's partition descriptor. Note that the checking in
> >> ExecConstraints() is turned off when routing a tuple from the parent.
> >
> > [ Sorry for the slow response. ]
> >
> > Yeah, that's a problem. Maybe it's best to associate this data with
> > the childrels after all - or halfway in between, e.g. augment
> > pg_inherits with this information. After all, the performance problem
> > I was worried about above isn't really much of an issue: each backend
> > will build a relcache entry for the parent just once and then use it
> > for the lifetime of the session unless some invalidation occurs. So
> > if that takes a small amount of extra time, it's probably not really a
> > big deal. On the other hand, if we can't build the implicit
> > constraint for the child table without opening the parent, that's
> > probably going to cause us some serious inconvenience.
>
> Agreed. So I will stick with the existing approach.
>
> Thanks,
> Amit
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-07 10:28 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-07 10:28 UTC (permalink / raw)
To: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Amit Langote <amitlangote09@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Hi,
On 2016/09/07 17:56, Rajkumar Raghuwanshi wrote:
> Hi,
>
> I have a query regarding list partitioning,
>
> For example if I want to store employee data in a table, with "IT" dept
> employee in emp_p1 partition, "HR" dept employee in emp_p2 partition and if
> employee belongs to other than these two, should come in emp_p3 partition.
>
> In this case not sure how to create partition table. Do we have something
> like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
> list partition.
>
> create table employee (empid int, dept varchar) partition by list(dept);
> create table emp_p1 partition of employee for values in ('IT');
> create table emp_p2 partition of employee for values in ('HR');
> create table emp_p3 partition of employee for values in (??);
Sorry, no such feature is currently offered. It might be possible to
offer something like a "default" list partition which accepts values other
than those specified for other existing partitions. However, that means
if we add a non-default list partition after a default one has been
created, the implementation must make sure that it moves any values from
the default partition that now belong to the newly created partition.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-08 12:38 Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Rajkumar Raghuwanshi @ 2016-09-08 12:38 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Sep 7, 2016 at 3:58 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
>
> Hi,
>
> On 2016/09/07 17:56, Rajkumar Raghuwanshi wrote:
> > Hi,
> >
> > I have a query regarding list partitioning,
> >
> > For example if I want to store employee data in a table, with "IT" dept
> > employee in emp_p1 partition, "HR" dept employee in emp_p2 partition and
> if
> > employee belongs to other than these two, should come in emp_p3
> partition.
> >
> > In this case not sure how to create partition table. Do we have something
> > like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
> > list partition.
> >
> > create table employee (empid int, dept varchar) partition by list(dept);
> > create table emp_p1 partition of employee for values in ('IT');
> > create table emp_p2 partition of employee for values in ('HR');
> > create table emp_p3 partition of employee for values in (??);
>
> Sorry, no such feature is currently offered. It might be possible to
> offer something like a "default" list partition which accepts values other
> than those specified for other existing partitions. However, that means
> if we add a non-default list partition after a default one has been
> created, the implementation must make sure that it moves any values from
> the default partition that now belong to the newly created partition.
>
> Thanks,
> Amit
>
Thanks for clarifying, But I could see same problem of moving data when
adding a new non-default partition with unbounded range partition.
For example give here, Initially I have create a partition table test with
test_p3 as unbounded end,
Later tried to change test_p3 to contain 7-9 values only, and adding a new
partition test_p4 contain 10-unbound.
--create partition table and some leafs
CREATE TABLE test (a int, b int) PARTITION BY RANGE(a);
CREATE TABLE test_p1 PARTITION OF test FOR VALUES START (1) END (4);
CREATE TABLE test_p2 PARTITION OF test FOR VALUES START (4) END (7);
CREATE TABLE test_p3 PARTITION OF test FOR VALUES START (7) END UNBOUNDED;
--insert some data
INSERT INTO test SELECT i, i*10 FROM generate_series(1,3) i;
INSERT INTO test SELECT i, i*10 FROM generate_series(4,6) i;
INSERT INTO test SELECT i, i*10 FROM generate_series(7,13) i;
--directly not able to attach test_p4 because of overlap error, hence
detached test_p3 and than attaching test_p4
SELECT tableoid::regclass,* FROM test;
tableoid | a | b
----------+----+-----
test_p1 | 1 | 10
test_p1 | 2 | 20
test_p1 | 3 | 30
test_p2 | 4 | 40
test_p2 | 5 | 50
test_p2 | 6 | 60
test_p3 | 7 | 70
test_p3 | 8 | 80
test_p3 | 9 | 90
test_p3 | 10 | 100
test_p3 | 11 | 110
test_p3 | 12 | 120
test_p3 | 13 | 130
(13 rows)
ALTER TABLE test DETACH PARTITION test_p3;
CREATE TABLE test_p4 (like test);
ALTER TABLE test ATTACH PARTITION test_p4 FOR VALUES start (10) end
UNBOUNDED;
--now can not attach test_p3 because of overlap with test_p4, causing data
loss from main test table.
ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (10);
ERROR: source table contains a row violating partition bound specification
ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (13);
ERROR: partition "test_p3" would overlap partition "test_p4"
SELECT tableoid::regclass,* FROM test;
tableoid | a | b
----------+---+----
test_p1 | 1 | 10
test_p1 | 2 | 20
test_p1 | 3 | 30
test_p2 | 4 | 40
test_p2 | 5 | 50
test_p2 | 6 | 60
(6 rows)
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-09 01:43 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-09-09 01:43 UTC (permalink / raw)
To: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/09/08 21:38, Rajkumar Raghuwanshi wrote:
> On Wed, Sep 7, 2016 at 3:58 PM, Amit Langote wrote:
>> On 2016/09/07 17:56, Rajkumar Raghuwanshi wrote:
>>>
>>> In this case not sure how to create partition table. Do we have something
>>> like we have UNBOUNDED for range partition or oracle have "DEFAULT" for
>>> list partition.
>>>
>>> create table employee (empid int, dept varchar) partition by list(dept);
>>> create table emp_p1 partition of employee for values in ('IT');
>>> create table emp_p2 partition of employee for values in ('HR');
>>> create table emp_p3 partition of employee for values in (??);
>>
>> Sorry, no such feature is currently offered. It might be possible to
>> offer something like a "default" list partition which accepts values other
>> than those specified for other existing partitions. However, that means
>> if we add a non-default list partition after a default one has been
>> created, the implementation must make sure that it moves any values from
>> the default partition that now belong to the newly created partition.
>
> Thanks for clarifying, But I could see same problem of moving data when
> adding a new non-default partition with unbounded range partition.
>
> For example give here, Initially I have create a partition table test with
> test_p3 as unbounded end,
> Later tried to change test_p3 to contain 7-9 values only, and adding a new
> partition test_p4 contain 10-unbound.
>
> --create partition table and some leafs
> CREATE TABLE test (a int, b int) PARTITION BY RANGE(a);
> CREATE TABLE test_p1 PARTITION OF test FOR VALUES START (1) END (4);
> CREATE TABLE test_p2 PARTITION OF test FOR VALUES START (4) END (7);
> CREATE TABLE test_p3 PARTITION OF test FOR VALUES START (7) END UNBOUNDED;
>
> --insert some data
> INSERT INTO test SELECT i, i*10 FROM generate_series(1,3) i;
> INSERT INTO test SELECT i, i*10 FROM generate_series(4,6) i;
> INSERT INTO test SELECT i, i*10 FROM generate_series(7,13) i;
>
> --directly not able to attach test_p4 because of overlap error, hence
> detached test_p3 and than attaching test_p4
> SELECT tableoid::regclass,* FROM test;
> tableoid | a | b
> ----------+----+-----
> test_p1 | 1 | 10
> test_p1 | 2 | 20
> test_p1 | 3 | 30
> test_p2 | 4 | 40
> test_p2 | 5 | 50
> test_p2 | 6 | 60
> test_p3 | 7 | 70
> test_p3 | 8 | 80
> test_p3 | 9 | 90
> test_p3 | 10 | 100
> test_p3 | 11 | 110
> test_p3 | 12 | 120
> test_p3 | 13 | 130
> (13 rows)
>
> ALTER TABLE test DETACH PARTITION test_p3;
> CREATE TABLE test_p4 (like test);
> ALTER TABLE test ATTACH PARTITION test_p4 FOR VALUES start (10) end
> UNBOUNDED;
>
> --now can not attach test_p3 because of overlap with test_p4, causing data
> loss from main test table.
> ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (10);
> ERROR: source table contains a row violating partition bound specification
> ALTER TABLE test ATTACH PARTITION test_p3 FOR VALUES start (7) end (13);
> ERROR: partition "test_p3" would overlap partition "test_p4"
In this particular case, you will have to move any rows in test_p3 with
key > or >= 10 into the new partition test_p4 using dml (to not lose any
data). Then attach test_p3 as partition for values start (7) end (10);
you won't get either of the above errors.
Looking forward, what we need I think is a split partition command.
Adding a new partition that overlaps the default list partition or
unbounded range partition could be done by splitting the latter. Perhaps
something like:
alter table test
split partition test_p3 at (10) [inclusive | exclusive] with test_p4;
The above command would make test_p3 into 2 partitions:
test_p3 start (7) end (10) [inclusive | exclusive]
test_p4 start (10) [exclusive | inclusive] end unbounded
Any rows in test_p3 with key > or >= 10 will be moved into the newly
created test_p4 as part of the execution of this command.
For your list partitioning example:
create table employee (empid int, dept varchar) partition by list(dept);
create table emp_p1 partition of employee for values in ('IT');
create table emp_p2 partition of employee for values in ('HR');
create table emp_p3 partition of employee for values in (default);
alter table emp
split partition emp_p3 with emp_p3 ('ACCT') emp_p4 (default);
Any rows in emp_p3 with key != 'ACCT' will be moved into the newly created
default partition emp_p4.
But for time being, I think we could provide the syntax and mechanism for
default list partition seeing as we have the same for range partitioned
table (namely a range partition with unbounded start or end). Although
with the limitations as discussed.
Thoughts?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-09 08:55 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-09 08:55 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On 2016/09/06 22:04, Amit Langote wrote:
> Will fix.
Here is an updated set of patches.
In addition to fixing a couple of bugs reported by Ashutosh and Rajkumar,
there are a few of major changes:
* change the way individual partition bounds are represented internally
and the way a collection of partition bounds associated with a
partitioned table is exposed to other modules. Especially list
partition bounds which are manipulated more efficiently as discussed
at [1].
* \d partitioned_table now shows partition count and \d+ lists partition
names and their bounds as follows:
\d t6
Table "public.t6"
Column | Type | Modifiers
.-------+-------------------+-----------
a | integer |
b | character varying |
Partition Key: LIST (a)
Number of partitions: 3 (Use \d+ to list them.)
\d+ t6
Table "public.t6"
Column | Type | Modifiers | Storage | Stats target |
Description
.-------+-------------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | character varying | | extended | |
Partition Key: LIST (a)
Partitions: t6_p1 FOR VALUES IN (1, 2, NULL),
t6_p2 FOR VALUES IN (4, 5),
t6_p3 FOR VALUES IN (3, 6)
\d+ p
Table "public.p"
Column | Type | Modifiers | Storage | Stats target | Description
.-------+--------------+-----------+----------+--------------+-------------
a | integer | | plain | |
b | character(1) | | extended | |
Partition Key: RANGE (a)
Partitions: p1 FOR VALUES START (1) END (10),
p2 FOR VALUES START (10) END (20),
p3 FOR VALUES START (20) END (30),
p4 FOR VALUES START (30) EXCLUSIVE END (40) INCLUSIVE,
p5 FOR VALUES START (40) EXCLUSIVE END (50),
p6 FOR VALUES START (50) END UNBOUNDED
* Some more regression tests
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoZCr0-t93KgJA3T1uy9yWxfYaSYL3X35ObyHg%2BZUfERqQ%40mail...
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-4.patch (115.9K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-4.patch)
download | inline diff:
From 29c9d232c6ab3d45e14522fd9f9e11fdad06eb4d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.
2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 102 +++++++-
doc/src/sgml/ref/create_table.sgml | 55 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 6 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 2 +
src/backend/catalog/heap.c | 27 ++-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/partition.c | 394 +++++++++++++++++++++++++
src/backend/catalog/pg_depend.c | 3 +
src/backend/catalog/pg_partitioned_table.c | 172 +++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 7 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 384 ++++++++++++++++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 33 ++
src/backend/nodes/equalfuncs.c | 28 ++
src/backend/nodes/outfuncs.c | 26 ++
src/backend/parser/gram.y | 110 ++++++--
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 69 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/cache/relcache.c | 19 ++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 8 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 35 +++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/catalog/pg_partitioned_table_fn.h | 29 ++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 9 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 155 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 133 +++++++++
54 files changed, 2033 insertions(+), 45 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/backend/catalog/pg_partitioned_table.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned_table.h
create mode 100644 src/include/catalog/pg_partitioned_table_fn.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..0b38ff7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores information
+ about the partition key of tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <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 expression 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>
+ Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+ KEY constraints; however, you can define these constraints on individual
+ data partitions.
+ </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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
- pg_type.o storage.o toasting.o
+ pg_type.o storage.o toasting.o pg_partitioned_table.o
BKIFILES = postgres.bki postgres.description postgres.shdescription
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..aafd2e6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
+ /* Remove NO INHERIT flag if rel is a partitioned table */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
/*
* Create the Check Constraint
*/
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9aa8174..e0d56a9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..35e020c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ char **partcolnames; /* partition key column names */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprbin_item;
+ List *partexprsrc = NIL;
+ ListCell *partexprsrc_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ /* Allocate in the supposedly short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprbin,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+
+ /* We should have a partexprsrc as well */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprsrc,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+ partexprsrc = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather partition column names (simple C strings) */
+ key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+ /* Copy partattrs and fill other per-attribute info */
+ partexprbin_item = list_head(key->partexprs);
+ partexprsrc_item = list_head(partexprsrc);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+
+ /* Collect atttribute names */
+ if (key->partattrs[i] != 0)
+ key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+ key->partattrs[i]);
+ else
+ {
+ Value *str = lfirst(partexprsrc_item);
+ key->partcolnames[i] = pstrdup(str->val.str);
+ partexprsrc_item = lnext(partexprsrc_item);
+ }
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+ return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int i;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc0(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc0(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+ newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+ for (i = 0; i < newkey->partnatts; i++)
+ newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..fa4d0f5
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ * routines to support manipulation of the pg_partitioned_table relation
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_fn.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * StorePartitionKey
+ * Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprbinDatum;
+ Datum partexprsrcDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprbin)
+ {
+ char *exprbinString;
+ char *exprsrcString;
+
+ exprbinString = nodeToString(partexprbin);
+ exprsrcString = nodeToString(partexprsrc);
+ partexprbinDatum = CStringGetTextDatum(exprbinString);
+ partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+ pfree(exprbinString);
+ pfree(exprsrcString);
+ }
+ else
+ partexprbinDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprbinDatum)
+ {
+ nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+ nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+ }
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+ values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Store dependencies on anything mentioned in the key expressions.
+ * However, ignore the column references which causes self-dependencies
+ * to be created that are undesirable. That is done by asking the
+ * dependency-tracking sub-system to ignore any such dependencies.
+ */
+ if (partexprbin)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprbin,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 432b0ca..be3fbc9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1736,6 +1736,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..3bb5933 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -262,6 +264,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -433,6 +441,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass);
/* ----------------------------------------------------------------
@@ -596,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition key, if any */
+ if (stmt->partby)
+ {
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprbin = NIL;
+ List *partexprsrc = NIL;
+
+ stmt->partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(rel, stmt->partby->partParams,
+ partattrs, &partexprbin, &partexprsrc,
+ partopclass);
+
+ partnatts = list_length(stmt->partby->partParams);
+ StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+ partattrs, partexprbin, partexprsrc, partopclass);
+ }
+
/*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
@@ -955,7 +989,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+ * for OBJECT_TABLE relations. It is ok for the passed in relkind to be
+ * RELKIND_RELATION while the relation is actually a partitioned table.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1563,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4341,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4578,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_key_natts(key);
+ partexprs = get_partition_key_exprs(key);
+
+ partexpr_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partatt = get_partition_col_attnum(key, i);
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5705,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5749,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7906,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10067,6 +10231,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12066,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ char *exprsrc;
+
+ partattrs[attn] = 0; /* marks expression */
+ *partexprbin = lappend(*partexprbin, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ exprsrc = deparse_expression(expr,
+ deparse_context_for(RelationGetRelationName(rel),
+ RelationGetRelid(rel)),
+ false, false);
+ *partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+ }
+ }
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4f39dad..37a60b6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3017,6 +3017,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4173,6 +4174,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5087,6 +5114,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4800165..a321ac1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2633,6 +2634,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3386,6 +3408,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 90fecb1..ab75085 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3280,6 +3281,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3864,6 +3885,11 @@ outNode(StringInfo str, const void *obj)
break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..a95a65a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,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.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13782,6 +13855,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..71c0c1c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7a2950e..85d67c1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +608,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +623,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +646,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +717,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +733,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +749,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +769,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2518,6 +2586,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..41f9304 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..f19479d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -983,10 +983,13 @@ ProcessUtilitySlow(ParseState *pstate,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_TABLE
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8cbd6e7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1050,6 +1052,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2053,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5050,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+extern int16 get_partition_col_attnum(PartitionKey key, int col);
+extern Oid get_partition_col_typid(PartitionKey key, int col);
+extern int32 get_partition_col_typmod(PartitionKey key, int col);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..db54358
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprbin; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+ pg_node_tree partexprsrc;
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partexprbin 6
+#define Anum_pg_partitioned_table_partexprsrc 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ * prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8d3dcf4..afaa4d3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,6 +698,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1752,6 +1786,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..708232d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,158 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- CREATE TABLE PARTITION BY
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fail_fk (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE fail_unique (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE fail_exclusion (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+ a int
+) PARTIION BY RANGE (a, a);
+ERROR: syntax error at or near "PARTIION"
+LINE 3: ) PARTIION BY RANGE (a, a);
+ ^
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: partition key expression cannot return a set
+DROP FUNCTION retset(int);
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expression
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR: cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6e4a8be 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,136 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- CREATE TABLE PARTITION BY
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE fail_inh_partition_by (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE fail_two_col_list_key (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE fail_pk (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
+
+CREATE TABLE fail_fk (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE fail_unique (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_exclusion (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE fail_col_used_twice (
+ a int
+) PARTIION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_set_returning_expr_in_key (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-4.patch (22.5K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-4.patch)
download | inline diff:
From f8a83d23be2aca0ac374c0054ecbf7d1636e51af Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 140 ++++++++++++++++++++++++++++
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, 260 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.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,144 @@ 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 relid = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprbin))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprbin, &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);
+
+ 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 ba9c276..c805a84 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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,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");
@@ -5289,7 +5294,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 "
@@ -5301,7 +5307,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,
@@ -5315,7 +5321,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,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)
{
@@ -5947,6 +5955,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 */
@@ -5991,7 +6000,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: 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 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-4.patch (205.8K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-4.patch)
download | inline diff:
From 9dcfedb6ad2693c2ec85578b839190cac35d09d6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
contrib/pg_trgm/trgm_op.c | 2 +-
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/heap.c | 35 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1689 +++++++++++++++++++++++++++-
src/backend/catalog/pg_partitioned_table.c | 2 -
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 774 +++++++++++--
src/backend/nodes/copyfuncs.c | 48 +
src/backend/nodes/equalfuncs.c | 42 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 33 +
src/backend/parser/gram.y | 209 ++++-
src/backend/parser/parse_agg.c | 6 +
src/backend/parser/parse_expr.c | 23 +
src/backend/parser/parse_utilcmd.c | 331 ++++++-
src/backend/utils/cache/relcache.c | 104 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 39 +
src/include/catalog/pg_class.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/test/regress/expected/alter_table.out | 219 ++++
src/test/regress/expected/create_table.out | 188 +++
src/test/regress/sql/alter_table.sql | 192 ++++
src/test/regress/sql/create_table.sql | 138 +++
36 files changed, 4291 insertions(+), 166 deletions(-)
diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index dd0f492..1cfd341 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -1023,7 +1023,7 @@ trgm_presence_map(TRGM *query, TRGM *key)
result = (bool *) palloc0(lenq * sizeof(bool));
- /* for each query trigram, do a binary search in the key array */
+/* for each query trigram, do a binary search in the key array */
for (i = 0; i < lenq; i++)
{
int lo = 0;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b38ff7..444e9fe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1229,6 +1311,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index aafd2e6..16e4d22 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2054,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
- /* Remove NO INHERIT flag if rel is a partitioned table */
+ /* Remove NO INHERIT flag if rel is a partitioned table or a partition */
if (is_no_inherit &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2475,7 +2494,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 35e020c..530bc97 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* partition.c
- * Partitioning related utility functions.
+ * Partitioning related data structures and functions.
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
#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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
/* Type and collation information for partition key columns */
@@ -69,11 +77,151 @@ typedef struct PartitionKeyData
KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
} PartitionKeyData;
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; maps
+ * element-by-element with the values array */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+ int nvalues;
+ Datum *values;
+ bool has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+ Datum value;
+ int index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+ Oid oid;
+ PartitionRange *range;
+} RangePartition;
+
/* Support RelationBuildPartitionKey() */
static PartitionKey copy_partition_key(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ ListInfo *listinfo,
+ int *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ RangeInfo *rangeinfo,
+ int n,
+ int *with);
+
+/* Support get_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *get_qual_for_list(PartitionKey key, PartitionListSpec *list);
+static List *get_qual_for_range(PartitionKey key, PartitionRangeSpec *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+ PartitionListSpec *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+ bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
/*
* Partition key related functions
*/
@@ -392,3 +540,1542 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
return result;
}
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ ListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index;
+
+ /* Range partitioning */
+ RangePartition **range_parts;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ oids[i++] = partrelid;
+ }
+
+ /* Convert from node to a internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionList **list;
+ int j;
+
+ list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+ i = 0;
+ all_values_count = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(lfirst(cell), PartitionListSpec));
+ list_spec = (PartitionListSpec *) lfirst(cell);
+
+ list[i] = make_list_from_spec(key, list_spec);
+
+ if (list[i]->has_null)
+ {
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+ all_values_count += list[i]->nvalues;
+ i++;
+ }
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (ListValue **)
+ palloc0(all_values_count * sizeof(ListValue *));
+ j = 0;
+ for (i = 0; i < nparts; i++)
+ {
+ int k;
+
+ for (k = 0; k < list[i]->nvalues; k++)
+ {
+ ListValue *list_value;
+
+ list_value = (ListValue *) palloc0(sizeof(ListValue));
+ list_value->value = datumCopy(list[i]->values[k],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ list_value->index = i;
+ all_values[j++] = list_value;
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ range_parts = (RangePartition **)
+ palloc0(nparts * sizeof(RangePartition *));
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionRangeSpec *range_spec;
+ RangePartition *range_part;
+
+ Assert(IsA(lfirst(cell), PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) lfirst(cell);
+ range_part = (RangePartition *)
+ palloc0(nparts * sizeof(RangePartition));
+ range_part->oid = oids[i];
+ range_part->range = make_range_from_spec(key, range_spec);
+ range_parts[i++] = range_part;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->bounds = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ ListInfo *listinfo;
+
+ listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+
+ /*
+ * Copy oids in the same order as they were found in the
+ * pg_inherits catalog
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[i] = oids[i];
+
+ /* Sort so that we can perform binary search over values */
+ qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+ list_value_cmp, (void *) key);
+
+ listinfo->nvalues = all_values_count;
+ listinfo->has_null = found_null_partition;
+ if (found_null_partition)
+ listinfo->null_index = null_partition_index;
+ else
+ listinfo->null_index = -1;
+ listinfo->values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo->indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo->values[i] = datumCopy(all_values[i]->value,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ listinfo->indexes[i] = all_values[i]->index;
+ }
+
+ result->bounds->listinfo = listinfo;
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ RangeInfo *rangeinfo;
+
+ rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+ rangeinfo->ranges = (PartitionRange **)
+ palloc0(nparts * sizeof(PartitionRange *));
+
+ /*
+ * Sort so that we can perform binary search over ranges.
+ * Note that this will also sort oids together.
+ */
+ qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+ range_partition_cmp, (void *) key);
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = range_parts[i]->oid;
+ rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+ key);
+ }
+
+ result->bounds->rangeinfo = rangeinfo;
+ break;
+ }
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2, int n)
+{
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+ return false;
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent. Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ Relation parent = heap_open(parentId, NoLock); /* already locked */
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+
+ if (pdesc->nparts > 0)
+ {
+ ListInfo *listinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->listinfo);
+ listinfo = pdesc->bounds->listinfo;
+ if (list_overlaps_existing_partition(key, list, listinfo,
+ &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, list->location)));
+ }
+ }
+ break;
+ }
+
+ case PARTITION_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("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ RangeInfo *rangeinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+ rangeinfo = pdesc->bounds->rangeinfo;
+ if (range_overlaps_existing_partition(key, range, rangeinfo,
+ pdesc->nparts, &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, range->location)));
+ }
+ }
+ break;
+ }
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ int i;
+ ListCell *partexprs_item;
+
+ Assert(key);
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ Assert(key->strategy == PARTITION_STRAT_LIST);
+ my_qual = get_qual_for_list(key, list_spec);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ Assert(key->strategy == PARTITION_STRAT_RANGE);
+ my_qual = get_qual_for_range(key, range_spec);
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ /* Find all attributes referenced and translate each reference */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+ return list_values_cmp((PartitionKey) arg,
+ (*(const ListValue **) a)->value,
+ (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+ return partition_range_cmp((PartitionKey) arg,
+ (*(const RangePartition **) a)->range,
+ (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ ListInfo *listinfo,
+ int *with)
+{
+ int i;
+ PartitionList *new_list;
+
+ Assert(listinfo);
+ Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+ new_list = make_list_from_spec(key, list_spec);
+
+ if (new_list->has_null && listinfo->has_null)
+ {
+ *with = listinfo->null_index;
+ return true;
+ }
+
+ for (i = 0; i < new_list->nvalues; i++)
+ {
+ int found;
+
+ /* bsearch a new list's value in listinfo->values */
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ new_list->values[i],
+ key);
+ if (found >= 0)
+ {
+ *with = listinfo->indexes[found];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRange *range;
+ PartitionRangeBound *lower,
+ *upper;
+
+ range = make_range_from_spec(key, range_spec);
+ lower = range->lower;
+ upper = range->upper;
+
+ /*
+ * Range is not empty if one (and only one) of the bounds is infinity.
+ * Both cannot be infinity because of how the syntax is specified.
+ */
+ Assert(!lower->infinite || !upper->infinite);
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper < lower, then it's outright empty. Also if lower = upper
+ * and either is exclusive.
+ */
+ if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+ (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ RangeInfo *rangeinfo,
+ int n, int *with)
+{
+ int i;
+ PartitionRange *range;
+
+ /* Create internal representation of range from range_spec */
+ range = make_range_from_spec(key, range_spec);
+
+ Assert(rangeinfo);
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+ {
+ *with = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+ partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+ return true;
+
+ if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+ partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * translate_var_attno
+ *
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionListSpec (right operand). If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionListSpec *list_spec)
+{
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, leave out the test but remove null Const from the list
+ * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The
+ * latter because null-valued expressions does not have the desired
+ * behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(list_spec->values); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ list_spec->values = list_delete_cell(list_spec->values,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ arr->array_collid = key->tcinfo->typcoll[0];
+ arr->element_typeid = key->tcinfo->typid[0];
+ arr->elements = list_spec->values;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->tcinfo->typcoll[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ return nulltest1 ? list_make2(nulltest1, opexpr)
+ : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+ list_make2(nulltest2, opexpr),
+ -1))
+ : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionRangeSpec (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, 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 *nulltest;
+
+ if (spec->lower != NIL)
+ {
+ values = spec->lower;
+ islower = true;
+ inclusive = spec->lowerinc;
+ }
+ else
+ {
+ values = spec->upper;
+ islower = false;
+ inclusive = spec->upperinc;
+ }
+
+ /* Left operand */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->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 */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+
+ /* Build the opexpr and return the list containing it and nulltest */
+ return list_make2(nulltest,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->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 *nulltest;
+
+ /* 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 */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Is lower_val = upper_val?
+ */
+
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->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
+ */
+ 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 ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /* Turn that bound into a list of equivalent check quals */
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionListSpec
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec)
+{
+ PartitionList *list;
+ ListCell *cell;
+ int i,
+ num_non_null;
+
+
+ list = (PartitionList *) palloc0(sizeof(PartitionList));
+ list->has_null = false;
+
+ /* Never put a null into the values array, flag instead */
+ num_non_null = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->has_null = true;
+ else
+ num_non_null++;
+ }
+
+ list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+ i = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (!val->constisnull)
+ list->values[i++] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ }
+
+ list->nvalues = num_non_null;
+
+ return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+ int i;
+ int *mapping;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ if (list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /*
+ * Index start from one. A zero in nth slot means that partition n of
+ * the first collection has not yet been mapped with a partition from
+ * the second collection.
+ */
+ mapping = (int *) palloc0((n + 1) * sizeof(int));
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ int l1_partno = l1->indexes[i] + 1,
+ l2_partno = l2->indexes[i] + 1;
+
+ /* Encountered l1_partno for the first time, map to l2_partno */
+ if (mapping[l1_partno] == 0)
+ mapping[l1_partno] = l2_partno;
+ /*
+ * Maintain that the mapping does not change, otherwise report
+ * inequality
+ */
+ else if (mapping[l1_partno] != l2_partno)
+ return false;
+ }
+
+ /* Check that nulls are accepted in mapped partitions */
+ Assert(l1->has_null || l1->null_index == -1);
+ Assert(l2->has_null || l2->null_index == -1);
+ if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1)
+ return false;
+
+ return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionRangeSpec
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRange *range;
+
+ range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ range->lower = make_range_bound(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ range->upper = make_range_bound(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+
+ return range;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->infinite = (val == NIL);
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+ PartitionRange *result;
+
+ result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ result->lower = copy_range_bound(src->lower, key);
+ result->upper = copy_range_bound(src->upper, key);
+
+ return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+ int i;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->lower,
+ r2->ranges[i]->lower) != 0)
+ return false;
+
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->upper,
+ r2->ranges[i]->upper) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ int cmp;
+
+ cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+ if (cmp == 0)
+ cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+ return cmp;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Compare two composite keys
+ */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index fa4d0f5..453feb2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
DEPENDENCY_IGNORE);
- /* Tell world about the key */
- CacheInvalidateRelcache(rel);
heap_close(pg_partitioned_table, RowExclusiveLock);
heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3bb5933..4a7c98d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -164,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -280,7 +282,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);
@@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprbin, List **partexprsrc,
Oid *partopclass);
+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);
/* ----------------------------------------------------------------
@@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -589,10 +598,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
@@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
* for OBJECT_TABLE relations. It is ok for the passed in relkind to be
@@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1452,7 +1494,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;
@@ -1497,8 +1540,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),
@@ -1525,6 +1568,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),
@@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1709,6 +1783,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;
@@ -1837,6 +1912,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)
{
@@ -1866,16 +1944,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);
@@ -1912,8 +1994,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 */
@@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2581,11 +2669,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2827,8 +2916,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3107,6 +3199,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);
@@ -3424,6 +3521,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);
@@ -3494,7 +3597,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3743,6 +3852,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);
@@ -3928,7 +4043,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4008,6 +4123,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4072,6 +4188,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4261,6 +4386,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4458,7 +4589,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4780,6 +4912,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5302,6 +5439,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5832,6 +5989,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5924,9 +6086,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6315,8 +6479,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7877,7 +8043,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);
@@ -7936,8 +8104,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8009,6 +8179,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10176,6 +10351,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10188,12 +10368,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;
@@ -10238,37 +10413,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10303,6 +10452,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);
@@ -10317,16 +10529,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;
}
/*
@@ -10377,7 +10581,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
@@ -10395,12 +10599,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10422,14 +10630,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10440,8 +10652,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10456,7 +10670,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10470,7 +10686,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.
@@ -10489,10 +10705,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10539,7 +10759,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10547,7 +10769,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10558,6 +10782,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10571,7 +10796,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10582,6 +10809,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10595,13 +10862,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];
@@ -10610,19 +10875,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10632,7 +10888,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);
@@ -10653,11 +10909,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
@@ -10666,7 +10931,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)))
@@ -10728,7 +10993,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);
@@ -10759,7 +11024,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)
@@ -10771,30 +11036,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;
}
/*
@@ -12431,3 +12686,272 @@ ComputePartitionAttrs(Relation rel, 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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 37a60b6..3e800fe 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3017,6 +3018,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);
@@ -4200,6 +4202,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
* ****************************************************************
@@ -5120,6 +5159,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 a321ac1..7dfe8c0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2375,6 +2376,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);
@@ -2655,6 +2657,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
*/
@@ -3414,6 +3447,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 ab75085..f9972c1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2587,6 +2588,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);
@@ -3302,6 +3304,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'
@@ -3891,6 +3912,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 894a48f..3dfdc37 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2265,6 +2265,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
@@ -2496,6 +2525,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 a95a65a..50657af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -601,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -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;
+ }
;
/*****************************************************************************
@@ -11149,6 +11348,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;
@@ -13748,6 +13948,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13794,6 +13995,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13836,6 +14038,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 71c0c1c..6d4645c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -364,6 +364,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 */
@@ -1732,6 +1752,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:
@@ -3364,6 +3385,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 85d67c1..9d0438c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +239,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
@@ -249,11 +258,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")));
@@ -360,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);
@@ -899,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;
@@ -1118,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;
@@ -2587,6 +2602,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2675,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;
@@ -3039,3 +3071,300 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_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_partition_col_name(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_partition_col_name(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_partition_col_name(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 8cbd6e7..cd4f7f4 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);
/*
@@ -925,6 +927,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->bounds != NULL)
+ {
+ if (pdesc2->bounds == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+ pdesc1->nparts))
+ return false;
+ }
+ else if (pdesc2->bounds != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1052,13 +1107,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2055,6 +2115,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2203,11 +2267,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2218,6 +2283,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);
@@ -2248,6 +2314,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
@@ -2303,6 +2372,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 */
@@ -3529,6 +3605,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5052,6 +5142,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..f515d4d 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,10 +14,38 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
typedef struct PartitionKeyData *PartitionKey;
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection bounds; /* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
/* relcache support for partition key information */
extern void RelationBuildPartitionKey(Relation relation);
@@ -32,4 +60,15 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
extern int32 get_partition_col_typmod(PartitionKey key, int col);
extern char *get_partition_col_name(PartitionKey key, int col);
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_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 afaa4d3..b925b89 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) */
@@ -733,6 +734,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
****************************************************************************/
@@ -1560,7 +1595,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
@@ -1785,7 +1822,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 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR: source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2fec847..2b12276 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -434,3 +434,191 @@ Table "public.describe_list_key"
Partition Key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_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 table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 4dd6a0a..c38312e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -415,3 +415,141 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-4.patch (20.6K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-4.patch)
download | inline diff:
From 385ab8bfceafe99f4bcd9b44735f9c524c09ef27 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 85 +++++++++++++++++++++----
src/test/regress/expected/create_table.out | 39 +++++++++++
src/test/regress/sql/create_table.sql | 12 ++++
7 files changed, 393 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03be202..4bc4e91 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,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 c805a84..40ab7cc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6134,6 +6134,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15311,6 +15368,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 */
@@ -15339,8 +15407,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;
@@ -15369,7 +15440,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");
}
@@ -15434,15 +15506,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 (");
@@ -15612,6 +15691,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 10d924a..0b763ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2b12276..732bf80 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,45 @@ 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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition Key: LIST (a)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- 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 c38312e..8f52a85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,18 @@ 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partition cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-4.patch (14.5K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-4.patch)
download | inline diff:
From e47f1315277691f6562a6229a81556ceaed461b7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
src/backend/optimizer/prep/prepunion.c | 278 ++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
2 files changed, 204 insertions(+), 83 deletions(-)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..193b2c9 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /* Do not flatten the inheritance hierarchy if partitioned table */
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (childOID != parentOID)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
- */
- if (oldrc)
- {
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
- }
-
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-4.patch (30.2K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-4.patch)
download | inline diff:
From b8aea0365d49ede203fce9b90266c32dc69d4abb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be3fbc9..157d219 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,7 +2488,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 5790edc..5b0e8cf 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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 e28477d..e35da66 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/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-4.patch (8.0K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-4.patch)
download | inline diff:
From b67976c0eb99b7fe044b0c7f0bfc6300d58af069 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 206 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 211 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 530bc97..fb1ab0e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -160,6 +160,61 @@ typedef struct RangePartition
PartitionRange *range;
} RangePartition;
+/*
+ * 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,
@@ -201,6 +256,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
/* Support RelationGetPartitionQual() */
static List *generate_partition_qual(Relation rel, bool recurse);
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static PartitionList *make_list_from_spec(PartitionKey key,
PartitionListSpec *list_spec);
@@ -1067,6 +1126,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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_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 */
/*
@@ -1711,6 +1817,106 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 f515d4d..d3e789a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -45,6 +45,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support for partition key information */
extern void RelationBuildPartitionKey(Relation relation);
@@ -71,4 +72,8 @@ extern Oid get_partition_parent(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-4.patch (41.5K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-4.patch)
download | inline diff:
From 690ef5127fd8f7d81b954e1462b65ace542e8f60 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 347 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 ++++-
src/backend/executor/nodeModifyTable.c | 123 +++++++++++
src/backend/optimizer/plan/createplan.c | 60 ++++++
src/backend/optimizer/util/plancat.c | 20 ++-
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, 911 insertions(+), 12 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fb1ab0e..8f6cbc9 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -260,6 +260,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 PartitionList *make_list_from_spec(PartitionKey key,
PartitionListSpec *list_spec);
@@ -280,6 +292,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
PartitionRangeBound *b2);
static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
/*
* Partition key related functions
@@ -1167,7 +1182,7 @@ get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
node = node->next;
}
else
- result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+ result = lappend_oid(result, ptnode->pdesc->oids[i]);
}
return result;
@@ -1833,7 +1848,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;
@@ -1851,7 +1866,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
prev = NULL;
for (i = 0; i < parent->pdesc->nparts; i++)
{
- Oid relid = parent->pdesc->parts[i]->oid;
+ Oid relid = parent->pdesc->oids[i];
int offset;
Relation rel;
PartitionTreeNode child;
@@ -1917,6 +1932,267 @@ 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)
+{
+ Relation partRel;
+ 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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ ListInfo *listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->bounds->listinfo != NULL);
+ listinfo = pdesc->bounds->listinfo;
+
+ if (isnull && listinfo->has_null)
+ return listinfo->null_index;
+ else if (!isnull)
+ {
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ value,
+ key);
+ if (found >= 0)
+ return listinfo->indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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);
+ Assert(pdesc->bounds->rangeinfo != NULL);
+
+ return bsearch_ranges(key, pdesc->nparts,
+ pdesc->bounds->rangeinfo, tuple);
+}
+
/* List partition related support functions */
/*
@@ -2285,3 +2561,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
return result;
}
+
+/*
+ * bsearch_ranges
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = n - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (rangeinfo->ranges[idx]->upper->infinite)
+ {
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+ return idx;
+
+ break;
+ }
+ else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+ {
+ if (rangeinfo->ranges[idx]->lower->infinite)
+ return idx;
+
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->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, PartitionRangeBound *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, PartitionRangeBound *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 157d219..932ed62 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.
@@ -1382,6 +1388,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1677,6 +1771,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1690,6 +1786,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);
}
@@ -2240,6 +2353,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2260,7 +2374,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2368,6 +2483,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2395,6 +2511,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;
@@ -2416,10 +2533,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 */
@@ -2471,6 +2624,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 */
@@ -2491,7 +2669,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)
@@ -2521,7 +2708,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),
@@ -2541,6 +2729,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2554,7 +2748,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 4a7c98d..152c575 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1236,6 +1236,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 5b0e8cf..cb47035 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,79 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +2063,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 47158f6..32f4031 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_table_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6152,6 +6153,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 (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ 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 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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..9f87f57 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_table_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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 d3e789a..badd566 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"
@@ -76,4 +78,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 e35da66..39ca517 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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-4.patch (24.7K, ../../28ee345c-1278-700e-39a7-36a71f9a3b43@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-4.patch)
download | inline diff:
From 724d3b127c5a13a36cbaab77adfb5535ff89a871 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 a393813..253eeb4 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-14 07:58 Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Rajkumar Raghuwanshi @ 2016-09-14 07:58 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
I have Continued with testing declarative partitioning with the latest
patch. Got some more observation, given below
-- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
partition.
create table test_range_bound ( a int) partition by range(a);
--creating a partition to contain records {1,2,3,4}, by default 1 is
inclusive and 5 is exclusive
create table test_range_bound_p1 partition of test_range_bound for values
start (1) end (5);
--now trying to create a partition by explicitly mentioning start is
exclusive to contain records {5,6,7}, here trying to create with START with
4 as exclusive so range should be 5 to 8, but getting partition overlap
error.
create table test_range_bound_p2 partition of test_range_bound for values
start (4) EXCLUSIVE end (8);
ERROR: partition "test_range_bound_p2" would overlap partition
"test_range_bound_p1"
-- Observation 2 : able to create sub-partition out of the range set for
main table, causing not able to insert data satisfying any of the partition.
create table test_subpart (c1 int) partition by range (c1);
create table test_subpart_p1 partition of test_subpart for values start (1)
end (100) inclusive partition by range (c1);
create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
start (101) end (200);
\d+ test_subpart
Table "public.test_subpart"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Key: RANGE (c1)
Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE
\d+ test_subpart_p1
Table "public.test_subpart_p1"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
c1 | integer | | plain | |
Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
Partition Key: RANGE (c1)
Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)
insert into test_subpart values (50);
ERROR: no partition of relation "test_subpart_p1" found for row
DETAIL: Failing row contains (50).
insert into test_subpart values (150);
ERROR: no partition of relation "test_subpart" found for row
DETAIL: Failing row contains (150).
-- Observation 3 : Getting cache lookup failed, when selecting list
partition table containing array.
CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');
INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);
postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
tableoid | i | j | k
---------------+---+-----+-----
test_array_p1 | 1 | {1} | {1}
(1 row)
postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
tableoid | i | j | k
---------------+---+-------+-----
test_array_p2 | 2 | {2,2} | {2}
(1 row)
postgres=# SELECT tableoid::regclass,* FROM test_array;
ERROR: cache lookup failed for type 0
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
On Fri, Sep 9, 2016 at 2:25 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> On 2016/09/06 22:04, Amit Langote wrote:
> > Will fix.
>
> Here is an updated set of patches.
>
> In addition to fixing a couple of bugs reported by Ashutosh and Rajkumar,
> there are a few of major changes:
>
> * change the way individual partition bounds are represented internally
> and the way a collection of partition bounds associated with a
> partitioned table is exposed to other modules. Especially list
> partition bounds which are manipulated more efficiently as discussed
> at [1].
>
> * \d partitioned_table now shows partition count and \d+ lists partition
> names and their bounds as follows:
>
> \d t6
> Table "public.t6"
> Column | Type | Modifiers
> .-------+-------------------+-----------
> a | integer |
> b | character varying |
> Partition Key: LIST (a)
> Number of partitions: 3 (Use \d+ to list them.)
>
> \d+ t6
> Table "public.t6"
> Column | Type | Modifiers | Storage | Stats target |
> Description
> .-------+-------------------+-----------+----------+--------
> ------+-------------
> a | integer | | plain | |
> b | character varying | | extended | |
> Partition Key: LIST (a)
> Partitions: t6_p1 FOR VALUES IN (1, 2, NULL),
> t6_p2 FOR VALUES IN (4, 5),
> t6_p3 FOR VALUES IN (3, 6)
>
> \d+ p
> Table "public.p"
> Column | Type | Modifiers | Storage | Stats target | Description
> .-------+--------------+-----------+----------+-------------
> -+-------------
> a | integer | | plain | |
> b | character(1) | | extended | |
> Partition Key: RANGE (a)
> Partitions: p1 FOR VALUES START (1) END (10),
> p2 FOR VALUES START (10) END (20),
> p3 FOR VALUES START (20) END (30),
> p4 FOR VALUES START (30) EXCLUSIVE END (40) INCLUSIVE,
> p5 FOR VALUES START (40) EXCLUSIVE END (50),
> p6 FOR VALUES START (50) END UNBOUNDED
>
> * Some more regression tests
>
> Thanks,
> Amit
>
> [1]
> https://www.postgresql.org/message-id/CA%2BTgmoZCr0-
> t93KgJA3T1uy9yWxfYaSYL3X35ObyHg%2BZUfERqQ%40mail.gmail.com
>
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-15 08:53 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 4 replies; 329+ messages in thread
From: Amit Langote @ 2016-09-15 08:53 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi
On 2016/09/09 17:55, Amit Langote wrote:
> On 2016/09/06 22:04, Amit Langote wrote:
>> Will fix.
>
> Here is an updated set of patches.
An email from Rajkumar somehow managed to break out of this thread.
Quoting his message below so that I don't end up replying with patches on
two different threads.
On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:
> I have Continued with testing declarative partitioning with the latest
> patch. Got some more observation, given below
Thanks a lot for testing.
> -- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
> partition.
>
> create table test_range_bound ( a int) partition by range(a);
> --creating a partition to contain records {1,2,3,4}, by default 1 is
> inclusive and 5 is exclusive
> create table test_range_bound_p1 partition of test_range_bound for values
> start (1) end (5);
> --now trying to create a partition by explicitly mentioning start is
> exclusive to contain records {5,6,7}, here trying to create with START with
> 4 as exclusive so range should be 5 to 8, but getting partition overlap
> error.
> create table test_range_bound_p2 partition of test_range_bound for values
> start (4) EXCLUSIVE end (8);
> ERROR: partition "test_range_bound_p2" would overlap partition
> "test_range_bound_p1"
Wow, this is bad. What is needed in this case is "canonicalization" of
the range partition bounds specified in the command. Range types do this
and hence an equivalent test done with range type values would disagree
with the result given by the patch for range partition bounds.
select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
cmp
-----
f
(1 row)
In this case, the second range is converted into its equivalent canonical
form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
ranges do not overlap after all. Range type operators can do this because
their code can rely on the availability of a canonicalization function for
a given range type. From the range types documentation:
"""
A discrete range type should have a canonicalization function that is
aware of the desired step size for the element type. The canonicalization
function is charged with converting equivalent values of the range type to
have identical representations, in particular consistently inclusive or
exclusive bounds. If a canonicalization function is not specified, then
ranges with different formatting will always be treated as unequal, even
though they might represent the same set of values in reality.
"""
to extend the last sentence:
"... or consider two ranges overlapping when in reality they are not
(maybe they are really just adjacent)."
Within the code handling range partition bound, no such canonicalization
happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
hence ranges overlap.
To mitigate this, how about we restrict range partition key to contain
columns of only those types for which we know we can safely canonicalize a
range bound (ie, discrete range types)? I don't think we can use, say,
existing int4range_canonical but will have to write a version of it for
partitioning usage (range bounds of partitions are different from what
int4range_canonical is ready to handle). This approach will be very
limiting as then range partitions will be limited to columns of int,
bigint and date type only.
One more option is we let the user specify the canonicalize function next
to the column name when defining the partition key. If not specified, we
hard-code one for the types for which we will be implementing a
canonicalize function (ie, above mentioned types). In other cases, we
just don't have one and hence if an unexpected result occurs when creating
a new partition, it's up to the user to realize what happened. Of course,
we will be mentioning in the documentation why a canonicalize function is
necessary and how to write one. Note that this canonicalize function
comes into play only when defining new partitions, it has no role beyond
that point.
> -- Observation 2 : able to create sub-partition out of the range set for
> main table, causing not able to insert data satisfying any of the partition.
>
> create table test_subpart (c1 int) partition by range (c1);
> create table test_subpart_p1 partition of test_subpart for values start (1)
> end (100) inclusive partition by range (c1);
> create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
> start (101) end (200);
>
> \d+ test_subpart
> Table "public.test_subpart"
> Column | Type | Modifiers | Storage | Stats target | Description
> --------+---------+-----------+---------+--------------+-------------
> c1 | integer | | plain | |
> Partition Key: RANGE (c1)
> Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE
>
> \d+ test_subpart_p1
> Table "public.test_subpart_p1"
> Column | Type | Modifiers | Storage | Stats target | Description
> --------+---------+-----------+---------+--------------+-------------
> c1 | integer | | plain | |
> Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
> Partition Key: RANGE (c1)
> Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)
>
> insert into test_subpart values (50);
> ERROR: no partition of relation "test_subpart_p1" found for row
> DETAIL: Failing row contains (50).
> insert into test_subpart values (150);
> ERROR: no partition of relation "test_subpart" found for row
> DETAIL: Failing row contains (150).
It seems that DDL should prevent the same column being used in partition
key of lower level partitions. I don't know how much sense it would make,
but being able to use the same column as partition key of lower level
partitions may be a feature useful to some users if they know what they
are doing. But this last part doesn't sound like a good thing. I
modified the patch such that lower level partitions cannot use columns
used by ancestor tables.
> -- Observation 3 : Getting cache lookup failed, when selecting list
> partition table containing array.
>
> CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
> CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
> CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');
>
> INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
> INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);
>
> postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
> tableoid | i | j | k
> ---------------+---+-----+-----
> test_array_p1 | 1 | {1} | {1}
> (1 row)
>
> postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
> tableoid | i | j | k
> ---------------+---+-------+-----
> test_array_p2 | 2 | {2,2} | {2}
> (1 row)
>
> postgres=# SELECT tableoid::regclass,* FROM test_array;
> ERROR: cache lookup failed for type 0
That's a bug. Fixed in the attached patch.
PS: I'm going to have limited Internet access during this weekend and over
the next week, so responses could be slow. Sorry about that.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-5.patch (116.7K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-5.patch)
download | inline diff:
From 116cb85a500406843bff21138436e6f735025a34 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.
2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 102 +++++++-
doc/src/sgml/ref/create_table.sgml | 55 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 6 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 2 +
src/backend/catalog/heap.c | 27 ++-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/partition.c | 394 +++++++++++++++++++++++++
src/backend/catalog/pg_depend.c | 3 +
src/backend/catalog/pg_partitioned_table.c | 172 +++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 7 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 383 +++++++++++++++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 33 ++
src/backend/nodes/equalfuncs.c | 28 ++
src/backend/nodes/outfuncs.c | 26 ++
src/backend/parser/gram.y | 110 ++++++--
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 69 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/cache/relcache.c | 19 ++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 8 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 35 +++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/catalog/pg_partitioned_table_fn.h | 29 ++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 9 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 159 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 137 +++++++++
55 files changed, 2043 insertions(+), 45 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/backend/catalog/pg_partitioned_table.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned_table.h
create mode 100644 src/include/catalog/pg_partitioned_table_fn.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..0b38ff7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores information
+ about the partition key of tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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..331ed56 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <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 expression 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>
+ Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+ KEY constraints; however, you can define these constraints on individual
+ data partitions.
+ </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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..032d214 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,11 +11,11 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
- pg_type.o storage.o toasting.o
+ pg_type.o storage.o toasting.o pg_partitioned_table.o
BKIFILES = postgres.bki postgres.description postgres.shdescription
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..9e040ed 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
+ /* Remove NO INHERIT flag if rel is a partitioned table */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
/*
* Create the Check Constraint
*/
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9aa8174..e0d56a9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3706,6 +3708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..35e020c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct KeyTypeCollInfo
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ char **partcolnames; /* partition key column names */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+/* Support RelationBuildPartitionKey() */
+static PartitionKey copy_partition_key(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+
+/*
+ * Partition key related functions
+ */
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprbin_item;
+ List *partexprsrc = NIL;
+ ListCell *partexprsrc_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ /* Allocate in the supposedly short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprbin,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+
+ /* We should have a partexprsrc as well */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprsrc,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+ partexprsrc = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather partition column names (simple C strings) */
+ key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *));
+
+ /* Copy partattrs and fill other per-attribute info */
+ partexprbin_item = list_head(key->partexprs);
+ partexprsrc_item = list_head(partexprsrc);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprbin_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item));
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+
+ /* Collect atttribute names */
+ if (key->partattrs[i] != 0)
+ key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation),
+ key->partattrs[i]);
+ else
+ {
+ Value *str = lfirst(partexprsrc_item);
+ key->partcolnames[i] = pstrdup(str->val.str);
+ partexprsrc_item = lnext(partexprsrc_item);
+ }
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->tcinfo->typmod[col];
+}
+
+char *
+get_partition_col_name(PartitionKey key, int col)
+{
+ return key->partcolnames[col];
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int i;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc0(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc0(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+ newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *));
+ for (i = 0; i < newkey->partnatts; i++)
+ newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..fa4d0f5
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,172 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ * routines to support manipulation of the pg_partitioned_table relation
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_fn.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * StorePartitionKey
+ * Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprbinDatum;
+ Datum partexprsrcDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprbin)
+ {
+ char *exprbinString;
+ char *exprsrcString;
+
+ exprbinString = nodeToString(partexprbin);
+ exprsrcString = nodeToString(partexprsrc);
+ partexprbinDatum = CStringGetTextDatum(exprbinString);
+ partexprsrcDatum = CStringGetTextDatum(exprsrcString);
+ pfree(exprbinString);
+ pfree(exprsrcString);
+ }
+ else
+ partexprbinDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprbinDatum)
+ {
+ nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+ nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+ }
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+ values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Store dependencies on anything mentioned in the key expressions.
+ * However, ignore the column references which causes self-dependencies
+ * to be created that are undesirable. That is done by asking the
+ * dependency-tracking sub-system to ignore any such dependencies.
+ */
+ if (partexprbin)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprbin,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 432b0ca..be3fbc9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1736,6 +1736,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..04b60d5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -39,6 +40,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -262,6 +264,13 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ Oid relid;
+ const char *attname;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -433,6 +442,12 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, const char *attname, bool *is_expr);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass);
/* ----------------------------------------------------------------
@@ -596,7 +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_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -697,6 +713,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition key, if any */
+ if (stmt->partby)
+ {
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprbin = NIL;
+ List *partexprsrc = NIL;
+
+ stmt->partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(rel, stmt->partby->partParams,
+ partattrs, &partexprbin, &partexprsrc,
+ partopclass);
+
+ partnatts = list_length(stmt->partby->partParams);
+ StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+ partattrs, partexprbin, partexprsrc, partopclass);
+ }
+
/*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
@@ -955,7 +990,14 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
+ * for OBJECT_TABLE relations. It is ok for the passed in relkind to be
+ * RELKIND_RELATION while the relation is actually a partitioned table.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1335,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1564,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5745,78 @@ 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 *var = (Var *) node;
+ char *varattname = get_attname(context->relid, var->varattno);
+
+ if (!strcmp(varattname, context->attname))
+ 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, const char *attname, bool *is_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_key_natts(key);
+ partexprs = get_partition_key_exprs(key);
+
+ partexpr_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+ char *partattname = get_partition_col_name(key, i);
+
+ if(partattno != 0)
+ {
+ if (is_expr)
+ *is_expr = false;
+ if (!strcmp(attname, partattname))
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ if (is_expr)
+ *is_expr = true;
+ context.relid = RelationGetRelid(rel);
+ context.attname = attname;
+ 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 +5831,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 +5876,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, colName, &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 +6407,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7861,6 +8007,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 +8038,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, colName, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7906,7 +8066,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8933,6 +9094,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9557,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9817,7 +9980,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10180,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10067,6 +10236,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11445,6 +11621,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11894,7 +12071,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12048,6 +12225,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12059,3 +12237,196 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for an partition key expression.
+ *
+ * DefineRelation() will make more checks.
+ */
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, List **partexprsrc,
+ Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ char *exprsrc;
+
+ partattrs[attn] = 0; /* marks expression */
+ *partexprbin = lappend(*partexprbin, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ exprsrc = deparse_expression(expr,
+ deparse_context_for(RelationGetRelationName(rel),
+ RelationGetRelid(rel)),
+ false, false);
+ *partexprsrc = lappend(*partexprsrc, makeString(exprsrc));
+ }
+ }
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..7f79665 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,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);
@@ -4174,6 +4175,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
* ****************************************************************
@@ -5088,6 +5115,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 29a090f..a3f990b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,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
*/
@@ -3387,6 +3409,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 7e092d7..349d65f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3281,6 +3282,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 -
@@ -3865,6 +3886,11 @@ outNode(StringInfo str, const void *obj)
break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..a95a65a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,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.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13782,6 +13855,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..7f496ea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..d1de990 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..aef5e7f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +608,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +623,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +646,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +717,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +733,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +749,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +769,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..f19479d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -983,10 +983,13 @@ ProcessUtilitySlow(ParseState *pstate,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_TABLE
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8cbd6e7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1050,6 +1052,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2053,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5050,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..502bc1a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..9c266c1
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* relcache support for partition key information */
+extern void RelationBuildPartitionKey(Relation relation);
+
+/* Partition key inquiry functions */
+extern int get_partition_key_strategy(PartitionKey key);
+extern int get_partition_key_natts(PartitionKey key);
+extern List *get_partition_key_exprs(PartitionKey key);
+
+/* Partition key inquiry functions - for a given column */
+extern int16 get_partition_col_attnum(PartitionKey key, int col);
+extern Oid get_partition_col_typid(PartitionKey key, int col);
+extern int32 get_partition_col_typmod(PartitionKey key, int col);
+extern char *get_partition_col_name(PartitionKey key, int col);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..db54358
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprbin; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+ pg_node_tree partexprsrc;
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partexprbin 6
+#define Anum_pg_partitioned_table_partexprsrc 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..918ce79
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ * prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ List *partexprsrc,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2f7efa8..c4abdf7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..2b50f8a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,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
@@ -1753,6 +1787,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 6633586..99f68c7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..07de59f 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +535,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..0af1422 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,162 @@ 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: set-returning functions are not allowed in partition key expression
+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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use a constant expression as partition key
+DROP FUNCTION const_func();
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+ERROR: cannot use COLLATE in partition key expression
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..418ac55 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ 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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexist_col (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_system_col_key (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- cannot use COLLATE in partition key
+CREATE TABLE fail_collate_key (
+ a text
+) PARTITION BY RANGE ((a COLLATE "default"));
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-5.patch (22.5K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-5.patch)
download | inline diff:
From f63606c810b60bbeb5d9ee40af54d3f78282a6b2 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 140 ++++++++++++++++++++++++++++
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, 260 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.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,144 @@ 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 relid = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprbin))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprbin, &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);
+
+ 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 ba9c276..c805a84 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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,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");
@@ -5289,7 +5294,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 "
@@ -5301,7 +5307,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,
@@ -5315,7 +5321,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,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)
{
@@ -5947,6 +5955,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 */
@@ -5991,7 +6000,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition Key: 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 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-5.patch (207.5K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-5.patch)
download | inline diff:
From a705f53fd0ced82031be6c81e446d8f8d6498124 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
contrib/pg_trgm/trgm_op.c | 2 +-
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/heap.c | 35 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1714 +++++++++++++++++++++++++++-
src/backend/catalog/pg_partitioned_table.c | 2 -
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 863 ++++++++++++--
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 | 1 -
src/backend/parser/parse_utilcmd.c | 325 ++++++-
src/backend/utils/cache/relcache.c | 104 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 40 +
src/include/catalog/pg_class.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 | 2 +-
src/include/utils/rel.h | 9 +
src/test/regress/expected/alter_table.out | 219 ++++
src/test/regress/expected/create_table.out | 190 +++
src/test/regress/sql/alter_table.sql | 192 ++++
src/test/regress/sql/create_table.sql | 140 +++
35 files changed, 4373 insertions(+), 168 deletions(-)
diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index dd0f492..1cfd341 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -1023,7 +1023,7 @@ trgm_presence_map(TRGM *query, TRGM *key)
result = (bool *) palloc0(lenq * sizeof(bool));
- /* for each query trigram, do a binary search in the key array */
+/* for each query trigram, do a binary search in the key array */
for (i = 0; i < lenq; i++)
{
int lo = 0;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b38ff7..444e9fe 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6f51cbc..421a134 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1229,6 +1311,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 331ed56..2e7ded6 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>
@@ -1422,7 +1492,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9e040ed..2fc404c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2042,13 +2054,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
- /* Remove NO INHERIT flag if rel is a partitioned table */
+ /* Remove NO INHERIT flag if rel is a partitioned table or a partition */
if (is_no_inherit &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2475,7 +2494,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b0b43cf..bc527a9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -871,7 +871,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 35e020c..2428aa8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* partition.c
- * Partitioning related utility functions.
+ * Partitioning related data structures and functions.
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -18,19 +18,26 @@
#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_partitioned_table.h"
+#include "catalog/pg_partitioned_table_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"
@@ -40,6 +47,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
/* Type and collation information for partition key columns */
@@ -69,11 +77,151 @@ typedef struct PartitionKeyData
KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
} PartitionKeyData;
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; maps
+ * element-by-element with the values array */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+ int nvalues;
+ Datum *values;
+ bool has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+ Datum value;
+ int index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+ Oid oid;
+ PartitionRange *range;
+} RangePartition;
+
/* Support RelationBuildPartitionKey() */
static PartitionKey copy_partition_key(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ ListInfo *listinfo,
+ int *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ RangeInfo *rangeinfo,
+ int n,
+ int *with);
+
+/* Support get_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *get_qual_for_list(PartitionKey key, PartitionListSpec *list);
+static List *get_qual_for_range(PartitionKey key, PartitionRangeSpec *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+ PartitionListSpec *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+ bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
/*
* Partition key related functions
*/
@@ -392,3 +540,1567 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
return result;
}
+
+/*
+ * Partition bound and partition descriptor related functions
+ */
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ ListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index;
+
+ /* Range partitioning */
+ RangePartition **range_parts;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ oids[i++] = partrelid;
+ }
+
+ /* Convert from node to a internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionList **list;
+ int j;
+
+ list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+ i = 0;
+ all_values_count = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(lfirst(cell), PartitionListSpec));
+ list_spec = (PartitionListSpec *) lfirst(cell);
+
+ list[i] = make_list_from_spec(key, list_spec);
+
+ if (list[i]->has_null)
+ {
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+ all_values_count += list[i]->nvalues;
+ i++;
+ }
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (ListValue **)
+ palloc0(all_values_count * sizeof(ListValue *));
+ j = 0;
+ for (i = 0; i < nparts; i++)
+ {
+ int k;
+
+ for (k = 0; k < list[i]->nvalues; k++)
+ {
+ ListValue *list_value;
+
+ list_value = (ListValue *) palloc0(sizeof(ListValue));
+ list_value->value = datumCopy(list[i]->values[k],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ list_value->index = i;
+ all_values[j++] = list_value;
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ range_parts = (RangePartition **)
+ palloc0(nparts * sizeof(RangePartition *));
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionRangeSpec *range_spec;
+ RangePartition *range_part;
+
+ Assert(IsA(lfirst(cell), PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) lfirst(cell);
+ range_part = (RangePartition *)
+ palloc0(nparts * sizeof(RangePartition));
+ range_part->oid = oids[i];
+ range_part->range = make_range_from_spec(key, range_spec);
+ range_parts[i++] = range_part;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->bounds = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ ListInfo *listinfo;
+
+ listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+
+ /*
+ * Copy oids in the same order as they were found in the
+ * pg_inherits catalog
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[i] = oids[i];
+
+ /* Sort so that we can perform binary search over values */
+ qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+ list_value_cmp, (void *) key);
+
+ listinfo->nvalues = all_values_count;
+ listinfo->has_null = found_null_partition;
+ if (found_null_partition)
+ listinfo->null_index = null_partition_index;
+ else
+ listinfo->null_index = -1;
+ listinfo->values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo->indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo->values[i] = datumCopy(all_values[i]->value,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ listinfo->indexes[i] = all_values[i]->index;
+ }
+
+ result->bounds->listinfo = listinfo;
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ RangeInfo *rangeinfo;
+
+ rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+ rangeinfo->ranges = (PartitionRange **)
+ palloc0(nparts * sizeof(PartitionRange *));
+
+ /*
+ * Sort so that we can perform binary search over ranges.
+ * Note that this will also sort oids together.
+ */
+ qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+ range_partition_cmp, (void *) key);
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = range_parts[i]->oid;
+ rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+ key);
+ }
+
+ result->bounds->rangeinfo = rangeinfo;
+ break;
+ }
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2, int n)
+{
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+ return false;
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent. Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ Relation parent = heap_open(parentId, NoLock); /* already locked */
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+
+ if (pdesc->nparts > 0)
+ {
+ ListInfo *listinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->listinfo);
+ listinfo = pdesc->bounds->listinfo;
+ if (list_overlaps_existing_partition(key, list, listinfo,
+ &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, list->location)));
+ }
+ }
+ break;
+ }
+
+ case PARTITION_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("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ RangeInfo *rangeinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+ rangeinfo = pdesc->bounds->rangeinfo;
+ if (range_overlaps_existing_partition(key, range, rangeinfo,
+ pdesc->nparts, &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, range->location)));
+ }
+ }
+ break;
+ }
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition, that is, relid_is_partition(relid) returns true.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_partition_ancestors
+ *
+ * Returns list of ancestors all the way up to the root table
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+ Oid parentOID = InvalidOid;
+ Relation rel;
+
+ rel = heap_open(relid, AccessShareLock);
+ if (rel->rd_rel->relispartition)
+ parentOID = get_partition_parent(relid);
+ heap_close(rel, AccessShareLock);
+
+ if (!OidIsValid(parentOID))
+ return NIL;
+
+ return list_concat(list_make1_oid(parentOID),
+ get_partition_ancestors(parentOID));
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ int i;
+ ListCell *partexprs_item;
+
+ Assert(key);
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ Assert(key->strategy == PARTITION_STRAT_LIST);
+ my_qual = get_qual_for_list(key, list_spec);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ Assert(key->strategy == PARTITION_STRAT_RANGE);
+ my_qual = get_qual_for_range(key, range_spec);
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ /* Find all attributes referenced and translate each reference */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+ return list_values_cmp((PartitionKey) arg,
+ (*(const ListValue **) a)->value,
+ (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+ return partition_range_cmp((PartitionKey) arg,
+ (*(const RangePartition **) a)->range,
+ (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ ListInfo *listinfo,
+ int *with)
+{
+ int i;
+ PartitionList *new_list;
+
+ Assert(listinfo);
+ Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+ new_list = make_list_from_spec(key, list_spec);
+
+ if (new_list->has_null && listinfo->has_null)
+ {
+ *with = listinfo->null_index;
+ return true;
+ }
+
+ for (i = 0; i < new_list->nvalues; i++)
+ {
+ int found;
+
+ /* bsearch a new list's value in listinfo->values */
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ new_list->values[i],
+ key);
+ if (found >= 0)
+ {
+ *with = listinfo->indexes[found];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRange *range;
+ PartitionRangeBound *lower,
+ *upper;
+
+ range = make_range_from_spec(key, range_spec);
+ lower = range->lower;
+ upper = range->upper;
+
+ /*
+ * Range is not empty if one (and only one) of the bounds is infinity.
+ * Both cannot be infinity because of how the syntax is specified.
+ */
+ Assert(!lower->infinite || !upper->infinite);
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper < lower, then it's outright empty. Also if lower = upper
+ * and either is exclusive.
+ */
+ if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+ (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ RangeInfo *rangeinfo,
+ int n, int *with)
+{
+ int i;
+ PartitionRange *range;
+
+ /* Create internal representation of range from range_spec */
+ range = make_range_from_spec(key, range_spec);
+
+ Assert(rangeinfo);
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+ {
+ *with = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+ partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+ return true;
+
+ if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+ partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * translate_var_attno
+ *
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionListSpec (right operand). If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionListSpec *list_spec)
+{
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, leave out the test but remove null Const from the list
+ * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The
+ * latter because null-valued expressions does not have the desired
+ * behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(list_spec->values); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ list_spec->values = list_delete_cell(list_spec->values,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->tcinfo->typid[0])
+ ? get_array_type(key->tcinfo->typid[0])
+ : key->tcinfo->typid[0];
+ arr->array_collid = key->tcinfo->typcoll[0];
+ arr->element_typeid = key->tcinfo->typid[0];
+ arr->elements = list_spec->values;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->tcinfo->typcoll[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ return nulltest1 ? list_make2(nulltest1, opexpr)
+ : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+ list_make2(nulltest2, opexpr),
+ -1))
+ : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionRangeSpec (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, 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 *nulltest;
+
+ if (spec->lower != NIL)
+ {
+ values = spec->lower;
+ islower = true;
+ inclusive = spec->lowerinc;
+ }
+ else
+ {
+ values = spec->upper;
+ islower = false;
+ inclusive = spec->upperinc;
+ }
+
+ /* Left operand */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->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 */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+
+ /* Build the opexpr and return the list containing it and nulltest */
+ return list_make2(nulltest,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->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 *nulltest;
+
+ /* 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 */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Is lower_val = upper_val?
+ */
+
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->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
+ */
+ 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 ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->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_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /* Turn that bound into a list of equivalent check quals */
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionListSpec
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec)
+{
+ PartitionList *list;
+ ListCell *cell;
+ int i,
+ num_non_null;
+
+
+ list = (PartitionList *) palloc0(sizeof(PartitionList));
+ list->has_null = false;
+
+ /* Never put a null into the values array, flag instead */
+ num_non_null = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->has_null = true;
+ else
+ num_non_null++;
+ }
+
+ list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+ i = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (!val->constisnull)
+ list->values[i++] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ }
+
+ list->nvalues = num_non_null;
+
+ return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+ int i;
+ int *mapping;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ if (list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /*
+ * Index start from one. A zero in nth slot means that partition n of
+ * the first collection has not yet been mapped with a partition from
+ * the second collection.
+ */
+ mapping = (int *) palloc0((n + 1) * sizeof(int));
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ int l1_partno = l1->indexes[i] + 1,
+ l2_partno = l2->indexes[i] + 1;
+
+ /* Encountered l1_partno for the first time, map to l2_partno */
+ if (mapping[l1_partno] == 0)
+ mapping[l1_partno] = l2_partno;
+ /*
+ * Maintain that the mapping does not change, otherwise report
+ * inequality
+ */
+ else if (mapping[l1_partno] != l2_partno)
+ return false;
+ }
+
+ /* Check that nulls are accepted in mapped partitions */
+ Assert(l1->has_null || l1->null_index == -1);
+ Assert(l2->has_null || l2->null_index == -1);
+ if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1)
+ return false;
+
+ return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionRangeSpec
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRange *range;
+
+ range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ range->lower = make_range_bound(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ range->upper = make_range_bound(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+
+ return range;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->infinite = (val == NIL);
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+ PartitionRange *result;
+
+ result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ result->lower = copy_range_bound(src->lower, key);
+ result->upper = copy_range_bound(src->upper, key);
+
+ return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+ int i;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->lower,
+ r2->ranges[i]->lower) != 0)
+ return false;
+
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->upper,
+ r2->ranges[i]->upper) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ int cmp;
+
+ cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+ if (cmp == 0)
+ cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+ return cmp;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Compare two composite keys
+ */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
index fa4d0f5..453feb2 100644
--- a/src/backend/catalog/pg_partitioned_table.c
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -139,8 +139,6 @@ StorePartitionKey(Relation rel,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
DEPENDENCY_IGNORE);
- /* Tell world about the key */
- CacheInvalidateRelcache(rel);
heap_close(pg_partitioned_table, RowExclusiveLock);
heap_freetuple(tuple);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 04b60d5..2f70eb7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -67,6 +67,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -164,6 +165,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -281,7 +284,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 +452,13 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby)
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprbin, List **partexprsrc,
Oid *partopclass);
+static bool partattrs_overlap_ancestor_key(Relation rel, Oid parentId, int partnatts,
+ AttrNumber *partattrs, List *partexprbin);
+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);
/* ----------------------------------------------------------------
@@ -486,6 +497,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -590,10 +602,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
@@ -668,6 +686,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -691,6 +727,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -726,8 +763,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
ComputePartitionAttrs(rel, stmt->partby->partParams,
partattrs, &partexprbin, &partexprsrc,
partopclass);
-
partnatts = list_length(stmt->partby->partParams);
+
+ /*
+ * If this is a partition, check that set of partition key columns
+ * do not overlap those used by ancestors.
+ */
+ if (stmt->partbound &&
+ partattrs_overlap_ancestor_key(rel, linitial_oid(inheritOids),
+ partnatts, partattrs, partexprbin))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use column or expression from ancestor partition key")));
+
StorePartitionKey(rel, stmt->partby->strategy, partnatts,
partattrs, partexprbin, partexprsrc, partopclass);
}
@@ -990,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind
* for OBJECT_TABLE relations. It is ok for the passed in relkind to be
@@ -1075,7 +1130,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1453,7 +1509,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;
@@ -1498,8 +1555,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),
@@ -1526,6 +1583,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),
@@ -1561,18 +1630,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1710,6 +1798,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;
@@ -1838,6 +1927,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)
{
@@ -1867,16 +1959,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);
@@ -1913,8 +2009,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 */
@@ -2204,6 +2301,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2434,7 +2536,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2582,11 +2684,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2828,8 +2931,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3108,6 +3214,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);
@@ -3425,6 +3536,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);
@@ -3495,7 +3612,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3744,6 +3867,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);
@@ -3929,7 +4058,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4009,6 +4138,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4073,6 +4203,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4262,6 +4401,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4459,7 +4604,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4781,6 +4927,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5303,6 +5454,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5837,6 +6008,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5929,9 +6105,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6320,8 +6498,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7882,7 +8062,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);
@@ -7941,8 +8123,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8014,6 +8198,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10181,6 +10370,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10193,12 +10387,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;
@@ -10243,37 +10432,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10308,6 +10471,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);
@@ -10322,16 +10548,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;
}
/*
@@ -10382,7 +10600,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
@@ -10400,12 +10618,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10427,14 +10649,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10445,8 +10671,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10461,7 +10689,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10475,7 +10705,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.
@@ -10494,10 +10724,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10544,7 +10778,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10552,7 +10788,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10563,6 +10801,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10576,7 +10815,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10587,6 +10828,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10600,13 +10881,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];
@@ -10615,19 +10894,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
-
- /*
- * 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);
+ bool is_detach_partition = false;
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10637,7 +10907,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);
@@ -10658,11 +10928,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
@@ -10671,7 +10950,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)))
@@ -10733,7 +11012,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);
@@ -10764,7 +11043,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)
@@ -10776,30 +11055,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;
}
/*
@@ -12430,3 +12699,345 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+/*
+ * Check if a new table's set of partition key columns overlaps that of any
+ * of its ancestors.
+ */
+static bool
+partattrs_overlap_ancestor_key(Relation rel, Oid parentId, int partnatts,
+ AttrNumber *partattrs, List *partexprbin)
+{
+ bool overlaps = false;
+ List *ancestors;
+ ListCell *cell;
+ int i;
+ ListCell *expr_item;
+
+ ancestors = list_make1_oid(parentId);
+ ancestors = list_concat(ancestors, get_partition_ancestors(parentId));
+
+ expr_item = list_head(partexprbin);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber attno = partattrs[i];
+ List *attnames = NIL;
+
+ if (attno != 0)
+ attnames = lappend(attnames,
+ get_attname(RelationGetRelid(rel), attno));
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(expr_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ expr_item = lnext(expr_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attnames = lappend(attnames,
+ get_attname(RelationGetRelid(rel), attno));
+ }
+ }
+
+ foreach(cell, ancestors)
+ {
+ Relation ancestor;
+ ListCell *lc;
+
+ ancestor = heap_open(lfirst_oid(cell), AccessShareLock);
+
+ foreach(lc, attnames)
+ {
+ char *name = (char *) lfirst(lc);
+
+ /*
+ * Is the column a partition attribute in this ancestor table?
+ */
+ if (is_partition_attr(ancestor, name, NULL))
+ {
+ overlaps = true;
+ break;
+ }
+ }
+
+ heap_close(ancestor, AccessShareLock);
+ }
+ }
+
+ return overlaps;
+}
+
+/*
+ * 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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7f79665..ee30d48 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3018,6 +3019,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);
@@ -4201,6 +4203,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
* ****************************************************************
@@ -5121,6 +5160,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 a3f990b..f811a6f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2376,6 +2377,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2656,6 +2658,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
*/
@@ -3415,6 +3448,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 349d65f..273bb72 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2587,6 +2588,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);
@@ -3303,6 +3305,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'
@@ -3892,6 +3913,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 917e6c8..fc3a599 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,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
@@ -2497,6 +2526,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 a95a65a..50657af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -546,6 +547,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.
@@ -571,7 +580,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
@@ -587,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -601,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -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;
+ }
;
/*****************************************************************************
@@ -11149,6 +11348,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;
@@ -13748,6 +13948,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13794,6 +13995,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13836,6 +14038,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..086de94 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
-
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index aef5e7f..f429a5a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +239,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
@@ -249,11 +258,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")));
@@ -360,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);
@@ -899,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;
@@ -1118,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;
@@ -2581,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2663,6 +2679,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3027,3 +3059,294 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_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 *) bound;
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ get_partition_col_name(key, 0)),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_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 *) bound;
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(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)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 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_partition_col_name(key, i)),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ get_partition_col_name(key, i)),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8cbd6e7..cd4f7f4 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);
/*
@@ -925,6 +927,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->bounds != NULL)
+ {
+ if (pdesc2->bounds == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+ pdesc1->nparts))
+ return false;
+ }
+ else if (pdesc2->bounds != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1052,13 +1107,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2055,6 +2115,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2203,11 +2267,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2218,6 +2283,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);
@@ -2248,6 +2314,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
@@ -2303,6 +2372,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 */
@@ -3529,6 +3605,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5052,6 +5142,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 9c266c1..9ebba0c 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,10 +14,38 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
typedef struct PartitionKeyData *PartitionKey;
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection bounds; /* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
/* relcache support for partition key information */
extern void RelationBuildPartitionKey(Relation relation);
@@ -32,4 +60,16 @@ extern Oid get_partition_col_typid(PartitionKey key, int col);
extern int32 get_partition_col_typmod(PartitionKey key, int col);
extern char *get_partition_col_name(PartitionKey key, int col);
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_partition_ancestors(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c4abdf7..bb62112 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_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 2b50f8a..69699e7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -734,6 +735,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
****************************************************************************/
@@ -1561,7 +1596,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
@@ -1786,7 +1823,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 99f68c7..0818d9c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,7 @@ 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 */
} ParseExprKind;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 07de59f..53c7612 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -96,6 +96,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -541,6 +544,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/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR: source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 91ba1be..b3e987d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -438,3 +438,193 @@ Table "public.describe_list_key"
Partition Key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE fail_used_ancestor_key_col PARTITION OF parted FOR VALUES IN ('c') PARTITION BY LIST (lower(a));
+ERROR: cannot use column or expression from ancestor partition key
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+ERROR: "part_a" is a partition of "parted"
+HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index b75542d..733c073 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,143 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE fail_used_ancestor_key_col PARTITION OF parted FOR VALUES IN ('c') PARTITION BY LIST (lower(a));
+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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-5.patch (20.6K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-5.patch)
download | inline diff:
From b285f28ba6e93a838f3173dc3e6ad1d67c5e55d0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 85 +++++++++++++++++++++----
src/test/regress/expected/create_table.out | 39 +++++++++++
src/test/regress/sql/create_table.sql | 12 ++++
7 files changed, 393 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03be202..4bc4e91 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,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 c805a84..40ab7cc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6134,6 +6134,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15311,6 +15368,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 */
@@ -15339,8 +15407,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;
@@ -15369,7 +15440,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");
}
@@ -15434,15 +15506,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 (");
@@ -15612,6 +15691,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 10d924a..0b763ee 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index b3e987d..fac621b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -598,6 +598,45 @@ ERROR: cannot use column or expression from ancestor partition key
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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition Key: LIST (a)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- 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 733c073..5e45014 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -552,6 +552,18 @@ 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partition cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patch (14.5K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patch)
download | inline diff:
From 7e6a40b68583e9ec237dd2819bd1fdffd9d517c6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
---
src/backend/optimizer/prep/prepunion.c | 278 ++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
2 files changed, 204 insertions(+), 83 deletions(-)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..193b2c9 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,65 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /* Do not flatten the inheritance hierarchy if partitioned table */
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1490,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1528,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (childOID != parentOID)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
- */
- if (oldrc)
- {
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
- }
-
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1566,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-5.patch (30.2K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-5.patch)
download | inline diff:
From 03eeb16a971f26b59486a0a753a0928dc87c4905 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be3fbc9..157d219 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,7 +2488,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 5790edc..5b0e8cf 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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-5.patch (8.0K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-5.patch)
download | inline diff:
From 6a745f60505455f976a4fc4bf7b0cbbb19d94add Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 206 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 211 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 2428aa8..d341345 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -160,6 +160,61 @@ typedef struct RangePartition
PartitionRange *range;
} RangePartition;
+/*
+ * 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,
@@ -201,6 +256,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
/* Support RelationGetPartitionQual() */
static List *generate_partition_qual(Relation rel, bool recurse);
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static PartitionList *make_list_from_spec(PartitionKey key,
PartitionListSpec *list_spec);
@@ -1090,6 +1149,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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_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 */
/*
@@ -1736,6 +1842,106 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 9ebba0c..07e6d64 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -45,6 +45,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support for partition key information */
extern void RelationBuildPartitionKey(Relation relation);
@@ -72,4 +73,8 @@ extern List *get_partition_ancestors(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-5.patch (41.5K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-5.patch)
download | inline diff:
From b1e3e11967b46c9ce17e951908258e8ce0594f24 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 347 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 ++++-
src/backend/executor/nodeModifyTable.c | 123 +++++++++++
src/backend/optimizer/plan/createplan.c | 60 ++++++
src/backend/optimizer/util/plancat.c | 20 ++-
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, 911 insertions(+), 12 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index d341345..afc7d18 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -260,6 +260,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 PartitionList *make_list_from_spec(PartitionKey key,
PartitionListSpec *list_spec);
@@ -280,6 +292,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
PartitionRangeBound *b2);
static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
/*
* Partition key related functions
@@ -1190,7 +1205,7 @@ get_leaf_partition_oids_v2(PartitionTreeNode ptnode)
node = node->next;
}
else
- result = lappend_oid(result, ptnode->pdesc->parts[i]->oid);
+ result = lappend_oid(result, ptnode->pdesc->oids[i]);
}
return result;
@@ -1858,7 +1873,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;
@@ -1876,7 +1891,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset)
prev = NULL;
for (i = 0; i < parent->pdesc->nparts; i++)
{
- Oid relid = parent->pdesc->parts[i]->oid;
+ Oid relid = parent->pdesc->oids[i];
int offset;
Relation rel;
PartitionTreeNode child;
@@ -1942,6 +1957,267 @@ 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)
+{
+ Relation partRel;
+ 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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ ListInfo *listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->bounds->listinfo != NULL);
+ listinfo = pdesc->bounds->listinfo;
+
+ if (isnull && listinfo->has_null)
+ return listinfo->null_index;
+ else if (!isnull)
+ {
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ value,
+ key);
+ if (found >= 0)
+ return listinfo->indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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);
+ Assert(pdesc->bounds->rangeinfo != NULL);
+
+ return bsearch_ranges(key, pdesc->nparts,
+ pdesc->bounds->rangeinfo, tuple);
+}
+
/* List partition related support functions */
/*
@@ -2310,3 +2586,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
return result;
}
+
+/*
+ * bsearch_ranges
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = n - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (rangeinfo->ranges[idx]->upper->infinite)
+ {
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+ return idx;
+
+ break;
+ }
+ else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+ {
+ if (rangeinfo->ranges[idx]->lower->infinite)
+ return idx;
+
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->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, PartitionRangeBound *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, PartitionRangeBound *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 157d219..932ed62 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.
@@ -1382,6 +1388,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1677,6 +1771,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1690,6 +1786,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);
}
@@ -2240,6 +2353,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2260,7 +2374,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2368,6 +2483,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2395,6 +2511,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;
@@ -2416,10 +2533,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 */
@@ -2471,6 +2624,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 */
@@ -2491,7 +2669,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)
@@ -2521,7 +2708,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),
@@ -2541,6 +2729,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2554,7 +2748,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 2f70eb7..e6c206d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1251,6 +1251,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 5b0e8cf..cb47035 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,79 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +2063,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 47158f6..32f4031 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_table_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6152,6 +6153,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 (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ 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 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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 870fae3..5bdf03e 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_table_fn.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -798,8 +799,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 07e6d64..81a4b91 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"
@@ -77,4 +79,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 697c90f..6e51773 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"
@@ -1141,6 +1142,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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-5.patch (24.7K, ../../e5c1c9cf-3f5a-c4d7-6047-7351147aaef9@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-5.patch)
download | inline diff:
From f876649aa6a0b33f6d9e7bc4580aea5eb14d2f1a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 f43352c..8084029 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-15 12:58 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-15 12:58 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi Amit,
It looks like there is some problem while creating paramterized paths
for multi-level partitioned tables. Here's a longish testcase
CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (b);
CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((b + a));
CREATE TABLE prt1_l_p3_p1 PARTITION OF prt1_l_p3 FOR VALUES START
(1000) END (1100);
CREATE TABLE prt1_l_p3_p2 PARTITION OF prt1_l_p3 FOR VALUES START
(1100) END (1200);
INSERT INTO prt1_l SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 2) i;
CREATE TABLE uprt1_l AS SELECT * FROM prt1_l;
CREATE TABLE prt2_l (a int, b int, c varchar) PARTITION BY RANGE(b);
CREATE TABLE prt2_l_p1 PARTITION OF prt2_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (a);
CREATE TABLE prt2_l_p1_p1 PARTITION OF prt2_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt2_l_p1_p2 PARTITION OF prt2_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt2_l_p2 PARTITION OF prt2_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt2_l_p2_p1 PARTITION OF prt2_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt2_l_p2_p2 PARTITION OF prt2_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt2_l_p3 PARTITION OF prt2_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((a + b));
CREATE TABLE prt2_l_p3_p1 PARTITION OF prt2_l_p3 FOR VALUES START
(1000) END (1100);
CREATE TABLE prt2_l_p3_p2 PARTITION OF prt2_l_p3 FOR VALUES START
(1100) END (1200);
INSERT INTO prt2_l SELECT i, i, to_char(i, 'FM0000') FROM
generate_series(0, 599, 3) i;
CREATE TABLE uprt2_l AS SELECT * FROM prt2_l;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS
t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a =
t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b))
ss
ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND
t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
ERROR: could not devise a query plan for the given query
Let's replace the laterally referenced relation by an unpartitioned table.
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM uprt1_l t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS
t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a =
t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b))
ss
ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND
t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
ERROR: could not devise a query plan for the given query
Let's replace another partitioned table in the inner query with an
unpartitioned table.
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM uprt1_l t1 LEFT JOIN LATERAL
(SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.a AS
t3a, least(t1.a,t2.a,t3.a) FROM prt1_l t2 JOIN uprt2_l t3 ON (t2.a =
t3.b AND t2.b = t3.a AND t2.c = t3.c AND t2.b + t2.a = t3.a + t3.b))
ss
ON t1.a = ss.t2a AND t1.b = ss.t2a AND t1.c = ss.t2c AND
t1.b + t1.a = ss.t2a + ss.t2b WHERE t1.a % 25 = 0 ORDER BY t1.a;
ERROR: could not devise a query plan for the given query
Please check if you are able to reproduce these errors in your
repository. I made sure that I cleaned up all partition-wise join code
before testing this, but ... .
I tried to debug the problem somewhat. In set_append_rel_pathlist(),
it finds that at least one child has a parameterized path as the
cheapest path, so it doesn't create an unparameterized path for append
rel. At the same time there is a parameterization common to all the
children, so it doesn't create any path. There seem to be two problems
here
1. The children from second level onwards may not be getting
parameterized for lateral references. That seems unlikely but
possible.
2. Reparameterization should have corrected this, but
reparameterize_path() does not support AppendPaths.
On Thu, Sep 15, 2016 at 2:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> Hi
>
> On 2016/09/09 17:55, Amit Langote wrote:
>> On 2016/09/06 22:04, Amit Langote wrote:
>>> Will fix.
>>
>> Here is an updated set of patches.
>
> An email from Rajkumar somehow managed to break out of this thread.
> Quoting his message below so that I don't end up replying with patches on
> two different threads.
>
> On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:
>> I have Continued with testing declarative partitioning with the latest
>> patch. Got some more observation, given below
>
> Thanks a lot for testing.
>
>> -- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
>> partition.
>>
>> create table test_range_bound ( a int) partition by range(a);
>> --creating a partition to contain records {1,2,3,4}, by default 1 is
>> inclusive and 5 is exclusive
>> create table test_range_bound_p1 partition of test_range_bound for values
>> start (1) end (5);
>> --now trying to create a partition by explicitly mentioning start is
>> exclusive to contain records {5,6,7}, here trying to create with START with
>> 4 as exclusive so range should be 5 to 8, but getting partition overlap
>> error.
>> create table test_range_bound_p2 partition of test_range_bound for values
>> start (4) EXCLUSIVE end (8);
>> ERROR: partition "test_range_bound_p2" would overlap partition
>> "test_range_bound_p1"
>
> Wow, this is bad. What is needed in this case is "canonicalization" of
> the range partition bounds specified in the command. Range types do this
> and hence an equivalent test done with range type values would disagree
> with the result given by the patch for range partition bounds.
>
> select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
> cmp
> -----
> f
> (1 row)
>
> In this case, the second range is converted into its equivalent canonical
> form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
> ranges do not overlap after all. Range type operators can do this because
> their code can rely on the availability of a canonicalization function for
> a given range type. From the range types documentation:
>
> """
> A discrete range type should have a canonicalization function that is
> aware of the desired step size for the element type. The canonicalization
> function is charged with converting equivalent values of the range type to
> have identical representations, in particular consistently inclusive or
> exclusive bounds. If a canonicalization function is not specified, then
> ranges with different formatting will always be treated as unequal, even
> though they might represent the same set of values in reality.
> """
>
> to extend the last sentence:
>
> "... or consider two ranges overlapping when in reality they are not
> (maybe they are really just adjacent)."
>
> Within the code handling range partition bound, no such canonicalization
> happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
> hence ranges overlap.
>
> To mitigate this, how about we restrict range partition key to contain
> columns of only those types for which we know we can safely canonicalize a
> range bound (ie, discrete range types)? I don't think we can use, say,
> existing int4range_canonical but will have to write a version of it for
> partitioning usage (range bounds of partitions are different from what
> int4range_canonical is ready to handle). This approach will be very
> limiting as then range partitions will be limited to columns of int,
> bigint and date type only.
>
> One more option is we let the user specify the canonicalize function next
> to the column name when defining the partition key. If not specified, we
> hard-code one for the types for which we will be implementing a
> canonicalize function (ie, above mentioned types). In other cases, we
> just don't have one and hence if an unexpected result occurs when creating
> a new partition, it's up to the user to realize what happened. Of course,
> we will be mentioning in the documentation why a canonicalize function is
> necessary and how to write one. Note that this canonicalize function
> comes into play only when defining new partitions, it has no role beyond
> that point.
>
>> -- Observation 2 : able to create sub-partition out of the range set for
>> main table, causing not able to insert data satisfying any of the partition.
>>
>> create table test_subpart (c1 int) partition by range (c1);
>> create table test_subpart_p1 partition of test_subpart for values start (1)
>> end (100) inclusive partition by range (c1);
>> create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
>> start (101) end (200);
>>
>> \d+ test_subpart
>> Table "public.test_subpart"
>> Column | Type | Modifiers | Storage | Stats target | Description
>> --------+---------+-----------+---------+--------------+-------------
>> c1 | integer | | plain | |
>> Partition Key: RANGE (c1)
>> Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE
>>
>> \d+ test_subpart_p1
>> Table "public.test_subpart_p1"
>> Column | Type | Modifiers | Storage | Stats target | Description
>> --------+---------+-----------+---------+--------------+-------------
>> c1 | integer | | plain | |
>> Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
>> Partition Key: RANGE (c1)
>> Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)
>>
>> insert into test_subpart values (50);
>> ERROR: no partition of relation "test_subpart_p1" found for row
>> DETAIL: Failing row contains (50).
>> insert into test_subpart values (150);
>> ERROR: no partition of relation "test_subpart" found for row
>> DETAIL: Failing row contains (150).
>
> It seems that DDL should prevent the same column being used in partition
> key of lower level partitions. I don't know how much sense it would make,
> but being able to use the same column as partition key of lower level
> partitions may be a feature useful to some users if they know what they
> are doing. But this last part doesn't sound like a good thing. I
> modified the patch such that lower level partitions cannot use columns
> used by ancestor tables.
>
>> -- Observation 3 : Getting cache lookup failed, when selecting list
>> partition table containing array.
>>
>> CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
>> CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
>> CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');
>>
>> INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
>> INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
>> tableoid | i | j | k
>> ---------------+---+-----+-----
>> test_array_p1 | 1 | {1} | {1}
>> (1 row)
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
>> tableoid | i | j | k
>> ---------------+---+-------+-----
>> test_array_p2 | 2 | {2,2} | {2}
>> (1 row)
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array;
>> ERROR: cache lookup failed for type 0
>
> That's a bug. Fixed in the attached patch.
>
> PS: I'm going to have limited Internet access during this weekend and over
> the next week, so responses could be slow. Sorry about that.
>
> Thanks,
> Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-15 16:37 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-09-15 16:37 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Sep 15, 2016 at 4:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Wow, this is bad. What is needed in this case is "canonicalization" of
> the range partition bounds specified in the command.
I think we shouldn't worry about this. It seems like unnecessary
scope creep. All human beings capable of using PostgreSQL will
understand that there are no integers between 4 and 5, so any
practical impact on this would be on someone creating partitions
automatically. But if someone is creating partitions automatically
they are highly likely to be using the same EXCLUSIVE/INCLUSIVE
settings for all of the partitions, in which case this won't arise.
And if they aren't, then I think we should just make them deal with
this limitation in their code instead of dealing with it in our code.
This patch is plenty complicated enough already; introducing a whole
new canonicalization concept that will help practically nobody seems
to me to be going in the wrong direction. If somebody really cares
enough to want to try to fix this, they can submit a followup patch
someday.
> To mitigate this, how about we restrict range partition key to contain
> columns of only those types for which we know we can safely canonicalize a
> range bound (ie, discrete range types)? I don't think we can use, say,
> existing int4range_canonical but will have to write a version of it for
> partitioning usage (range bounds of partitions are different from what
> int4range_canonical is ready to handle). This approach will be very
> limiting as then range partitions will be limited to columns of int,
> bigint and date type only.
-1. That is letting the tail wag the dog. Let's leave it the way you
had it and be happy.
>> -- Observation 2 : able to create sub-partition out of the range set for
>> main table, causing not able to insert data satisfying any of the partition.
>>
>> create table test_subpart (c1 int) partition by range (c1);
>> create table test_subpart_p1 partition of test_subpart for values start (1)
>> end (100) inclusive partition by range (c1);
>> create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
>> start (101) end (200);
>
> It seems that DDL should prevent the same column being used in partition
> key of lower level partitions. I don't know how much sense it would make,
> but being able to use the same column as partition key of lower level
> partitions may be a feature useful to some users if they know what they
> are doing. But this last part doesn't sound like a good thing. I
> modified the patch such that lower level partitions cannot use columns
> used by ancestor tables.
I again disagree. If somebody defines partition bounds that make it
impossible to insert the data that they care about, that's operator
error. The fact that, across multiple levels, you can manage to make
it impossible to insert any data at all does not make it anything
other than operator error. If we take the contrary position that it's
the system's job to prevent this sort of thing, we may disallow some
useful cases, like partitioning by the year portion of a date and then
subpartitioning by the month portion of that same date.
I think we'll probably also find that we're making the code
complicated to no purpose. For example, now you have to check when
attaching a partition that it doesn't violate the rule; otherwise you
end up with a table that can't be created directly (and thus can't
survive dump-and-restore) but can be created indirectly by exploiting
loopholes in the checks. It's tempting to think that we can check
simple cases - e.g. if the parent and the child are partitioning on
the same exact column, the child's range should be contained within
the parent's range - but more complicated cases are tricky. Suppose
the table is range-partitioned on (a, b) and range-subpartitioned on
b. It's not trivial to figure out whether the set of values that the
user can insert into that partition is non-empty. If we allow
partitioning on expressions, then it quickly becomes altogether
impossible to deduce anything useful - unless you can solve the
halting problem.
And, really, why do we care? If the user creates a partitioning
scheme that permits no rows in some or all of the partitions, then
they will have an empty table that can be correctly dumped and
restored but which cannot be used for anything useful unless it is
modified first. They probably don't want that, but it's not any more
broken than a table inheritance setup with mutually exclusive CHECK
constraints, or for that matter a plain table with mutually exclusive
CHECK constraints - and we don't try to prohibit those things. This
patch is supposed to be implementing partitioning, not artificial
intelligence.
>> -- Observation 3 : Getting cache lookup failed, when selecting list
>> partition table containing array.
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array;
>> ERROR: cache lookup failed for type 0
>
> That's a bug. Fixed in the attached patch.
Now on this one I'm not going to argue with your analysis. :-)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-18 06:41 Amit Langote <amitlangote09@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-09-18 06:41 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Sep 15, 2016 at 10:07 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Thu, Sep 15, 2016 at 4:53 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Wow, this is bad. What is needed in this case is "canonicalization" of
>> the range partition bounds specified in the command.
>
> I think we shouldn't worry about this. It seems like unnecessary
> scope creep. All human beings capable of using PostgreSQL will
> understand that there are no integers between 4 and 5, so any
> practical impact on this would be on someone creating partitions
> automatically. But if someone is creating partitions automatically
> they are highly likely to be using the same EXCLUSIVE/INCLUSIVE
> settings for all of the partitions, in which case this won't arise.
> And if they aren't, then I think we should just make them deal with
> this limitation in their code instead of dealing with it in our code.
> This patch is plenty complicated enough already; introducing a whole
> new canonicalization concept that will help practically nobody seems
> to me to be going in the wrong direction. If somebody really cares
> enough to want to try to fix this, they can submit a followup patch
> someday.
>
>> To mitigate this, how about we restrict range partition key to contain
>> columns of only those types for which we know we can safely canonicalize a
>> range bound (ie, discrete range types)? I don't think we can use, say,
>> existing int4range_canonical but will have to write a version of it for
>> partitioning usage (range bounds of partitions are different from what
>> int4range_canonical is ready to handle). This approach will be very
>> limiting as then range partitions will be limited to columns of int,
>> bigint and date type only.
>
> -1. That is letting the tail wag the dog. Let's leave it the way you
> had it and be happy.
Alright, let's leave this as something to work out later. We will
have to document the fact that such limitation exists though, I'd
think.
>>> -- Observation 2 : able to create sub-partition out of the range set for
>>> main table, causing not able to insert data satisfying any of the partition.
>>>
>>> create table test_subpart (c1 int) partition by range (c1);
>>> create table test_subpart_p1 partition of test_subpart for values start (1)
>>> end (100) inclusive partition by range (c1);
>>> create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
>>> start (101) end (200);
>>
>> It seems that DDL should prevent the same column being used in partition
>> key of lower level partitions. I don't know how much sense it would make,
>> but being able to use the same column as partition key of lower level
>> partitions may be a feature useful to some users if they know what they
>> are doing. But this last part doesn't sound like a good thing. I
>> modified the patch such that lower level partitions cannot use columns
>> used by ancestor tables.
>
> I again disagree. If somebody defines partition bounds that make it
> impossible to insert the data that they care about, that's operator
> error. The fact that, across multiple levels, you can manage to make
> it impossible to insert any data at all does not make it anything
> other than operator error. If we take the contrary position that it's
> the system's job to prevent this sort of thing, we may disallow some
> useful cases, like partitioning by the year portion of a date and then
> subpartitioning by the month portion of that same date.
>
> I think we'll probably also find that we're making the code
> complicated to no purpose. For example, now you have to check when
> attaching a partition that it doesn't violate the rule; otherwise you
> end up with a table that can't be created directly (and thus can't
> survive dump-and-restore) but can be created indirectly by exploiting
> loopholes in the checks. It's tempting to think that we can check
> simple cases - e.g. if the parent and the child are partitioning on
> the same exact column, the child's range should be contained within
> the parent's range - but more complicated cases are tricky. Suppose
> the table is range-partitioned on (a, b) and range-subpartitioned on
> b. It's not trivial to figure out whether the set of values that the
> user can insert into that partition is non-empty. If we allow
> partitioning on expressions, then it quickly becomes altogether
> impossible to deduce anything useful - unless you can solve the
> halting problem.
>
> And, really, why do we care? If the user creates a partitioning
> scheme that permits no rows in some or all of the partitions, then
> they will have an empty table that can be correctly dumped and
> restored but which cannot be used for anything useful unless it is
> modified first. They probably don't want that, but it's not any more
> broken than a table inheritance setup with mutually exclusive CHECK
> constraints, or for that matter a plain table with mutually exclusive
> CHECK constraints - and we don't try to prohibit those things. This
> patch is supposed to be implementing partitioning, not artificial
> intelligence.
Agree with your arguments. Certainly, I overlooked some use cases
that my proposed solution would outright prevent. I withdraw it.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-19 20:06 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-09-19 20:06 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Mon, Aug 15, 2016 at 7:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> + if (partexprs)
>> + recordDependencyOnSingleRelExpr(&myself,
>> + (Node *) partexprs,
>> + RelationGetRelid(rel),
>> + DEPENDENCY_NORMAL,
>> + DEPENDENCY_IGNORE);
>>
>> I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
>> here. Instead, you could just add an additional Boolean argument to
>> recordDependencyOnSingleRelExpr. That seems less likely to create
>> bugs in unrelated portions of the code.
>
> I did consider a Boolean argument instead of a new DependencyType value,
> however it felt a bit strange to pass a valid value for the fifth argument
> (self_behavior) and then ask using a separate parameter that it (a
> self-dependency) is to be ignored. By the way, no pg_depend entry is
> created on such a call, so the effect of the new type's usage seems
> localized to me. Thoughts?
I think that's not a very plausible argument. If you add a fifth
argument to that function, then only that function needs to know about
the possibility of ignoring self-dependencies. If you add a new
dependency type, then everything that knows about DependencyType needs
to know about them. That's got to be a much larger surface area for
bugs. Also, if you look around a bit, I believe you will find other
examples of cases where one argument is used only for certain values
of some other argument. That's not a novel design pattern.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-20 13:41 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-09-20 13:41 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Sep 15, 2016 at 4:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> [ new patches ]
Re-reviewing 0001.
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
This documentation doesn't match pg_partition_table.h, which has
partexprsrc and partexprbin. I don't understand why it's a good idea
to have both, and there seem to be no comments or documentation
supporting that choice anywhere.
+ 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 expression must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
Both of the sentences in this paragraph that do not begin with "the"
need to begin with "the". (In my experience, it's generally a feature
of English as spoken in India that connecting words like "the" and "a"
are sometimes left out where non-Indian speakers of English would
include them, so it would be good to watch out for this issue in
general.)
Also, I think this should be rephrased a bit to be more clear about
how the partitioning key works, like this: The optional
<literal>PARTITION BY</literal> clause specifies a method of
partitioning the table. The table thus created is called a
<firstterm>partitioned</firstterm> table. The parenthesized list of
expressions forms the <firsttem>partitioning key</firstterm> for the
table. When using range partitioning, the partioning key can include
multiple columns or expressions, but for list partitioning, the
partitioning key must consist of a single column or expression. If no
btree operator class is specified when creating a partitioned table,
the default btree operator class for the datatype will be used. If
there is none, an error will be reported.
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
Why? None of the reloptions that pertain to heap seem relevant to a
relkind without storage.
But, ah, do partitioned tables have storage? I mean, do we end up
with an empty file, or no relfilenode at all? Can I CLUSTER, VACUUM,
etc. a partitioned table? It would seem cleaner for the parent to
have no relfilenode at all, but I'm guessing we might need some more
changes for that to work out.
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
Whitespace. Also, here and elsewhere, how about using alphabetical
order, or anyway preserving it insofar as the existing list is
alphabetized?
+ /* Remove NO INHERIT flag if rel is a partitioned table */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to
partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
The code and the comment disagree. I think the code is right and the
comment should be adjusted to say something like /* Partitioned tables
do not have storage, so a NO INHERIT constraint makes no sense. */
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
Wrong.
+} KeyTypeCollInfo;
I don't like this type name very much. Can we get "Part" in there someplace?
It doesn't seem to be very well-designed, either. The number of
entries in each array is determined by the partnatts flag in
PartitionKeyData, which has also got various other arrays whose
lengths are determined by partnatts. Why do we have some arrays in
one structure and some arrays in another structure? Would it hurt
anything to merge everything into one structure? Or could
PartitionKeyData include a field of type KeyTypeCollInfo rather than
KeyTypeCollInfo *, saving one pointer reference every place we access
this data?
+ /* Allocate in the supposedly short-lived working context */
Why supposedly?
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
Isn't the point of putting the fixed-length fields first that we can
use GETSTRUCT() here? And even for partattrs as the first
variable-length thing?
+ /*
+ * 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.
+ */
I'm a bit confused by this, because I would think this processing
ought to have been done before storing anything in the system
catalogs. I don't see why it should be necessary to do it again after
pulling data back out of the system catalogs.
+ Value *str = lfirst(partexprsrc_item);
+ key->partcolnames[i] = pstrdup(str->val.str);
Should have a blank line in between.
+/*
+ * Partition key information inquiry functions
+ */
+int
+get_partition_key_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+int
+get_partition_key_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+List *
+get_partition_key_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
+Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->tcinfo->typid[col];
+}
+
+int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->tcinfo->typmod[col];
+}
If we're going to add notation for this, I think we should use macros
(or static inline functions defined in the header file). Doing it
this way adds more cycles for no benefit.
+ newkey->partattrs = (AttrNumber *)
+ palloc0(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
It's wasteful to use palloc0 if you're immediately going to overwrite
every byte in the array. Use regular palloc instead.
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
Only if it happens to be convenient for the caller, which doesn't seem
to be the case here.
+ /* Only this can ever be NULL */
+ if (!partexprbinDatum)
+ {
+ nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+ nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
+ }
How can it be valid to have no partitioning expressions?
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
Is this really needed? Isn't the caller going to do something similar
pretty soon?
+ heap_freetuple(tuple);
Probably useless - might as well let the context reset clean it up.
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(rel, tuple);
You don't need CatalogUpdateIndexes() after a delete, only after an
insert or update.
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE))
That's broken. Note that all of the conditions are joined using &&,
so if any one of them fails then we won't throw an error. In
particular, it's no longer possible to throw an error when relkind is
not RELKIND_RELATION.
+/* 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 *var = (Var *) node;
+ char *varattname = get_attname(context->relid, var->varattno);
+
+ if (!strcmp(varattname, context->attname))
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
Hrm. The comment says we're matching on attnum, but the code says
we're matching on attname. is_partition_attr() has the same confusion
between comments and code. Maybe instead of this whole approach it
would be better to use pull_varattnos(), then get_attnum() to find the
attribute number for the one you want, then bms_is_member().
+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);
Why isn't this logic being invoked from transformCreateStmt()? Then
we could use the actual parseState for the query instead of a fake
one.
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key
expression")));
I assume there is a good reason for this seemingly-arbitrary
restriction, but there's no comment saying what it is. One thing
that's odd is that this will only prohibit a CollateExpr at the top
level, not in some more-deeply nested position. That seems
suspicious.
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
Evidently, the user did not do that, because you just prohibited the
second one of those.
+ 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")));
Do these checks parallel what we do for CHECK constraints? It might
be good to apply about the same level of rigor in both cases.
+ exprsrc = deparse_expression(expr,
+ deparse_context_for(RelationGetRelationName(rel),
+ RelationGetRelid(rel)),
+ false, false);
Why bother? The output of this doesn't seem like a useful thing to
store. The fact that we've done similar things elsewhere doesn't make
it a good idea. I think we did it in other cases because we used to
be dumber than we are now.
+ (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.")));
The hint is not really accurate, because the type may well have a
btree operator class. Just not a default one.
+ char relkind = ((CreateStmt *)
stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_TABLE
+ : RELKIND_RELATION;
Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
RELKIND_PARTITION_TABLE; }
+ RelationBuildPartitionKey(relation);
I wonder if RelationBuildPartitionKey should really be in relcache.c.
What do we do in similar cases?
+} PartitionBy;
Maybe PartitionSpec?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-22 05:42 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 2 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-22 05:42 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi Amit,
Following sequence of DDLs gets an error
--
-- multi-leveled partitions
--
CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
(250) PARTITION BY RANGE (b);
CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
(100) END (250);
CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
(500) PARTITION BY RANGE (c);
CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
('0250') END ('0400');
CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
('0400') END ('0500');
CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
(600) PARTITION BY RANGE ((b + a));
ERROR: cannot use column or expression from ancestor partition key
The last statement is trying create subpartitions by range (b + a),
which contains a partition key from ancestor partition key but is not
exactly same as that. In fact it contains some extra columns other
than the ancestor partition key columns. Why do we want to prohibit
such cases?
On Thu, Sep 15, 2016 at 2:23 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> Hi
>
> On 2016/09/09 17:55, Amit Langote wrote:
>> On 2016/09/06 22:04, Amit Langote wrote:
>>> Will fix.
>>
>> Here is an updated set of patches.
>
> An email from Rajkumar somehow managed to break out of this thread.
> Quoting his message below so that I don't end up replying with patches on
> two different threads.
>
> On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:
>> I have Continued with testing declarative partitioning with the latest
>> patch. Got some more observation, given below
>
> Thanks a lot for testing.
>
>> -- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
>> partition.
>>
>> create table test_range_bound ( a int) partition by range(a);
>> --creating a partition to contain records {1,2,3,4}, by default 1 is
>> inclusive and 5 is exclusive
>> create table test_range_bound_p1 partition of test_range_bound for values
>> start (1) end (5);
>> --now trying to create a partition by explicitly mentioning start is
>> exclusive to contain records {5,6,7}, here trying to create with START with
>> 4 as exclusive so range should be 5 to 8, but getting partition overlap
>> error.
>> create table test_range_bound_p2 partition of test_range_bound for values
>> start (4) EXCLUSIVE end (8);
>> ERROR: partition "test_range_bound_p2" would overlap partition
>> "test_range_bound_p1"
>
> Wow, this is bad. What is needed in this case is "canonicalization" of
> the range partition bounds specified in the command. Range types do this
> and hence an equivalent test done with range type values would disagree
> with the result given by the patch for range partition bounds.
>
> select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
> cmp
> -----
> f
> (1 row)
>
> In this case, the second range is converted into its equivalent canonical
> form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
> ranges do not overlap after all. Range type operators can do this because
> their code can rely on the availability of a canonicalization function for
> a given range type. From the range types documentation:
>
> """
> A discrete range type should have a canonicalization function that is
> aware of the desired step size for the element type. The canonicalization
> function is charged with converting equivalent values of the range type to
> have identical representations, in particular consistently inclusive or
> exclusive bounds. If a canonicalization function is not specified, then
> ranges with different formatting will always be treated as unequal, even
> though they might represent the same set of values in reality.
> """
>
> to extend the last sentence:
>
> "... or consider two ranges overlapping when in reality they are not
> (maybe they are really just adjacent)."
>
> Within the code handling range partition bound, no such canonicalization
> happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
> hence ranges overlap.
>
> To mitigate this, how about we restrict range partition key to contain
> columns of only those types for which we know we can safely canonicalize a
> range bound (ie, discrete range types)? I don't think we can use, say,
> existing int4range_canonical but will have to write a version of it for
> partitioning usage (range bounds of partitions are different from what
> int4range_canonical is ready to handle). This approach will be very
> limiting as then range partitions will be limited to columns of int,
> bigint and date type only.
>
> One more option is we let the user specify the canonicalize function next
> to the column name when defining the partition key. If not specified, we
> hard-code one for the types for which we will be implementing a
> canonicalize function (ie, above mentioned types). In other cases, we
> just don't have one and hence if an unexpected result occurs when creating
> a new partition, it's up to the user to realize what happened. Of course,
> we will be mentioning in the documentation why a canonicalize function is
> necessary and how to write one. Note that this canonicalize function
> comes into play only when defining new partitions, it has no role beyond
> that point.
>
>> -- Observation 2 : able to create sub-partition out of the range set for
>> main table, causing not able to insert data satisfying any of the partition.
>>
>> create table test_subpart (c1 int) partition by range (c1);
>> create table test_subpart_p1 partition of test_subpart for values start (1)
>> end (100) inclusive partition by range (c1);
>> create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
>> start (101) end (200);
>>
>> \d+ test_subpart
>> Table "public.test_subpart"
>> Column | Type | Modifiers | Storage | Stats target | Description
>> --------+---------+-----------+---------+--------------+-------------
>> c1 | integer | | plain | |
>> Partition Key: RANGE (c1)
>> Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE
>>
>> \d+ test_subpart_p1
>> Table "public.test_subpart_p1"
>> Column | Type | Modifiers | Storage | Stats target | Description
>> --------+---------+-----------+---------+--------------+-------------
>> c1 | integer | | plain | |
>> Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
>> Partition Key: RANGE (c1)
>> Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)
>>
>> insert into test_subpart values (50);
>> ERROR: no partition of relation "test_subpart_p1" found for row
>> DETAIL: Failing row contains (50).
>> insert into test_subpart values (150);
>> ERROR: no partition of relation "test_subpart" found for row
>> DETAIL: Failing row contains (150).
>
> It seems that DDL should prevent the same column being used in partition
> key of lower level partitions. I don't know how much sense it would make,
> but being able to use the same column as partition key of lower level
> partitions may be a feature useful to some users if they know what they
> are doing. But this last part doesn't sound like a good thing. I
> modified the patch such that lower level partitions cannot use columns
> used by ancestor tables.
>
>> -- Observation 3 : Getting cache lookup failed, when selecting list
>> partition table containing array.
>>
>> CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
>> CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
>> CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');
>>
>> INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
>> INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
>> tableoid | i | j | k
>> ---------------+---+-----+-----
>> test_array_p1 | 1 | {1} | {1}
>> (1 row)
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
>> tableoid | i | j | k
>> ---------------+---+-------+-----
>> test_array_p2 | 2 | {2,2} | {2}
>> (1 row)
>>
>> postgres=# SELECT tableoid::regclass,* FROM test_array;
>> ERROR: cache lookup failed for type 0
>
> That's a bug. Fixed in the attached patch.
>
> PS: I'm going to have limited Internet access during this weekend and over
> the next week, so responses could be slow. Sorry about that.
>
> Thanks,
> Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-22 07:32 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-22 07:32 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
For list partitions, the ListInfo stores the index maps for values
i.e. the index of the partition to which the value belongs. Those
indexes are same as the indexes in partition OIDs array and come from
the catalogs. In case a user creates two partitioned tables with
exactly same lists for partitions but specifies them in a different
order, the OIDs are stored in the order specified. This means that
index array for these tables come out different. equal_list_info()
works around that by creating an array of mappings and checks whether
that mapping is consistent for all values. This means we will create
the mapping as many times as equal_list_info() is called, which is
expected to be more than the number of time
RelationBuildPartitionDescriptor() is called. Instead, if we
"canonicalise" the indexes so that they come out exactly same for
similarly partitioned tables, we build the mapping only once and
arrange OIDs accordingly.
Here's patch to do that. I have ran make check with this and it didn't
show any failure. Please consider this to be included in your next set
of patches.
That helps partition-wise join as well. For partition-wise join (and
further optimizations for partitioned tables), we create a list of
canonical partition schemes. In this list two similarly partitioned
tables share partition scheme pointer. A join between relations with
same partition scheme pointer can be joined partition-wise. It's
important that the indexes in partition scheme match to the OIDs array
to find matching RelOptInfos for partition-wise join.
On Thu, Sep 22, 2016 at 11:12 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> Hi Amit,
> Following sequence of DDLs gets an error
> --
> -- multi-leveled partitions
> --
> CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
> CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
> (250) PARTITION BY RANGE (b);
> CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
> CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
> (100) END (250);
> CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
> (500) PARTITION BY RANGE (c);
> CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
> ('0250') END ('0400');
> CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
> ('0400') END ('0500');
> CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
> (600) PARTITION BY RANGE ((b + a));
> ERROR: cannot use column or expression from ancestor partition key
>
> The last statement is trying create subpartitions by range (b + a),
> which contains a partition key from ancestor partition key but is not
> exactly same as that. In fact it contains some extra columns other
> than the ancestor partition key columns. Why do we want to prohibit
> such cases?
>
> On Thu, Sep 15, 2016 at 2:23 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>
>> Hi
>>
>> On 2016/09/09 17:55, Amit Langote wrote:
>>> On 2016/09/06 22:04, Amit Langote wrote:
>>>> Will fix.
>>>
>>> Here is an updated set of patches.
>>
>> An email from Rajkumar somehow managed to break out of this thread.
>> Quoting his message below so that I don't end up replying with patches on
>> two different threads.
>>
>> On 2016/09/14 16:58, Rajkumar Raghuwanshi wrote:
>>> I have Continued with testing declarative partitioning with the latest
>>> patch. Got some more observation, given below
>>
>> Thanks a lot for testing.
>>
>>> -- Observation 1 : Getting overlap error with START with EXCLUSIVE in range
>>> partition.
>>>
>>> create table test_range_bound ( a int) partition by range(a);
>>> --creating a partition to contain records {1,2,3,4}, by default 1 is
>>> inclusive and 5 is exclusive
>>> create table test_range_bound_p1 partition of test_range_bound for values
>>> start (1) end (5);
>>> --now trying to create a partition by explicitly mentioning start is
>>> exclusive to contain records {5,6,7}, here trying to create with START with
>>> 4 as exclusive so range should be 5 to 8, but getting partition overlap
>>> error.
>>> create table test_range_bound_p2 partition of test_range_bound for values
>>> start (4) EXCLUSIVE end (8);
>>> ERROR: partition "test_range_bound_p2" would overlap partition
>>> "test_range_bound_p1"
>>
>> Wow, this is bad. What is needed in this case is "canonicalization" of
>> the range partition bounds specified in the command. Range types do this
>> and hence an equivalent test done with range type values would disagree
>> with the result given by the patch for range partition bounds.
>>
>> select '[1,5)'::int4range && '(4,8]'::int4range as cmp;
>> cmp
>> -----
>> f
>> (1 row)
>>
>> In this case, the second range is converted into its equivalent canonical
>> form viz. '[5, 9)'. Then comparison of bounds 5) and [5 can tell that the
>> ranges do not overlap after all. Range type operators can do this because
>> their code can rely on the availability of a canonicalization function for
>> a given range type. From the range types documentation:
>>
>> """
>> A discrete range type should have a canonicalization function that is
>> aware of the desired step size for the element type. The canonicalization
>> function is charged with converting equivalent values of the range type to
>> have identical representations, in particular consistently inclusive or
>> exclusive bounds. If a canonicalization function is not specified, then
>> ranges with different formatting will always be treated as unequal, even
>> though they might represent the same set of values in reality.
>> """
>>
>> to extend the last sentence:
>>
>> "... or consider two ranges overlapping when in reality they are not
>> (maybe they are really just adjacent)."
>>
>> Within the code handling range partition bound, no such canonicalization
>> happens, so comparison 5) and (4 ends up concluding that upper1 > lower2,
>> hence ranges overlap.
>>
>> To mitigate this, how about we restrict range partition key to contain
>> columns of only those types for which we know we can safely canonicalize a
>> range bound (ie, discrete range types)? I don't think we can use, say,
>> existing int4range_canonical but will have to write a version of it for
>> partitioning usage (range bounds of partitions are different from what
>> int4range_canonical is ready to handle). This approach will be very
>> limiting as then range partitions will be limited to columns of int,
>> bigint and date type only.
>>
>> One more option is we let the user specify the canonicalize function next
>> to the column name when defining the partition key. If not specified, we
>> hard-code one for the types for which we will be implementing a
>> canonicalize function (ie, above mentioned types). In other cases, we
>> just don't have one and hence if an unexpected result occurs when creating
>> a new partition, it's up to the user to realize what happened. Of course,
>> we will be mentioning in the documentation why a canonicalize function is
>> necessary and how to write one. Note that this canonicalize function
>> comes into play only when defining new partitions, it has no role beyond
>> that point.
>>
>>> -- Observation 2 : able to create sub-partition out of the range set for
>>> main table, causing not able to insert data satisfying any of the partition.
>>>
>>> create table test_subpart (c1 int) partition by range (c1);
>>> create table test_subpart_p1 partition of test_subpart for values start (1)
>>> end (100) inclusive partition by range (c1);
>>> create table test_subpart_p1_sub1 partition of test_subpart_p1 for values
>>> start (101) end (200);
>>>
>>> \d+ test_subpart
>>> Table "public.test_subpart"
>>> Column | Type | Modifiers | Storage | Stats target | Description
>>> --------+---------+-----------+---------+--------------+-------------
>>> c1 | integer | | plain | |
>>> Partition Key: RANGE (c1)
>>> Partitions: test_subpart_p1 FOR VALUES START (1) END (100) INCLUSIVE
>>>
>>> \d+ test_subpart_p1
>>> Table "public.test_subpart_p1"
>>> Column | Type | Modifiers | Storage | Stats target | Description
>>> --------+---------+-----------+---------+--------------+-------------
>>> c1 | integer | | plain | |
>>> Partition Of: test_subpart FOR VALUES START (1) END (100) INCLUSIVE
>>> Partition Key: RANGE (c1)
>>> Partitions: test_subpart_p1_sub1 FOR VALUES START (101) END (200)
>>>
>>> insert into test_subpart values (50);
>>> ERROR: no partition of relation "test_subpart_p1" found for row
>>> DETAIL: Failing row contains (50).
>>> insert into test_subpart values (150);
>>> ERROR: no partition of relation "test_subpart" found for row
>>> DETAIL: Failing row contains (150).
>>
>> It seems that DDL should prevent the same column being used in partition
>> key of lower level partitions. I don't know how much sense it would make,
>> but being able to use the same column as partition key of lower level
>> partitions may be a feature useful to some users if they know what they
>> are doing. But this last part doesn't sound like a good thing. I
>> modified the patch such that lower level partitions cannot use columns
>> used by ancestor tables.
>>
>>> -- Observation 3 : Getting cache lookup failed, when selecting list
>>> partition table containing array.
>>>
>>> CREATE TABLE test_array ( i int,j int[],k text[]) PARTITION BY LIST (j);
>>> CREATE TABLE test_array_p1 PARTITION OF test_array FOR VALUES IN ('{1}');
>>> CREATE TABLE test_array_p2 PARTITION OF test_array FOR VALUES IN ('{2,2}');
>>>
>>> INSERT INTO test_array (i,j[1],k[1]) VALUES (1,1,1);
>>> INSERT INTO test_array (i,j[1],j[2],k[1]) VALUES (2,2,2,2);
>>>
>>> postgres=# SELECT tableoid::regclass,* FROM test_array_p1;
>>> tableoid | i | j | k
>>> ---------------+---+-----+-----
>>> test_array_p1 | 1 | {1} | {1}
>>> (1 row)
>>>
>>> postgres=# SELECT tableoid::regclass,* FROM test_array_p2;
>>> tableoid | i | j | k
>>> ---------------+---+-------+-----
>>> test_array_p2 | 2 | {2,2} | {2}
>>> (1 row)
>>>
>>> postgres=# SELECT tableoid::regclass,* FROM test_array;
>>> ERROR: cache lookup failed for type 0
>>
>> That's a bug. Fixed in the attached patch.
>>
>> PS: I'm going to have limited Internet access during this weekend and over
>> the next week, so responses could be slow. Sorry about that.
>>
>> Thanks,
>> Amit
>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[invalid/octet-stream] canonical_partition_indexes.patch (4.0K, ../../CAFjFpRdYfNVMPYKyXLpUHuGwqC2CjmgtBe9RUeUU9yHX8CO3kw@mail.gmail.com/2-canonical_partition_indexes.patch)
download | inline diff:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index afc7d18..ccb8fde 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -785,15 +785,14 @@ RelationBuildPartitionDesc(Relation rel)
case PARTITION_STRAT_LIST:
{
ListInfo *listinfo;
+ int *mappings = (int *)palloc(sizeof(int) * nparts);
+ int next_index = 0;
listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
- /*
- * Copy oids in the same order as they were found in the
- * pg_inherits catalog
- */
+ /* Invalid mapping. */
for (i = 0; i < nparts; i++)
- result->oids[i] = oids[i];
+ mappings[i] = -1;
/* Sort so that we can perform binary search over values */
qsort_arg(all_values, all_values_count, sizeof(ListValue *),
@@ -801,23 +800,48 @@ RelationBuildPartitionDesc(Relation rel)
listinfo->nvalues = all_values_count;
listinfo->has_null = found_null_partition;
- if (found_null_partition)
- listinfo->null_index = null_partition_index;
- else
- listinfo->null_index = -1;
listinfo->values = (Datum *)
palloc0(all_values_count * sizeof(Datum));
listinfo->indexes = (int *)
palloc0(all_values_count * sizeof(int));
+
+ /*
+ * While copying the indexes, adjust them so that they are same
+ * for any two partitioning schemes with the same lists. One
+ * way to achieve this is to assign indexes in the same order
+ * as the least values in the lists.
+ */
for (i = 0; i < all_values_count; i++)
{
listinfo->values[i] = datumCopy(all_values[i]->value,
key->tcinfo->typbyval[0],
key->tcinfo->typlen[0]);
- listinfo->indexes[i] = all_values[i]->index;
+
+ /* If this index has no mapping assign one. */
+ if (mappings[all_values[i]->index] == -1)
+ mappings[all_values[i]->index] = next_index++;
+
+ listinfo->indexes[i] = mappings[all_values[i]->index];
}
+ /* Assign a valid mapping to partition with NULLL values. */
+ if (mappings[null_partition_index] == -1)
+ mappings[null_partition_index] = next_index++;
+
+ /* All partitions get a valid mapping. */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo->null_index = mappings[null_partition_index];
+ else
+ listinfo->null_index = -1;
+
+ /* Re-arrange partition OIDs as per mappings. */
+ for (i = 0; i < nparts; i++)
+ result->oids[i] = oids[mappings[i]];
+
result->bounds->listinfo = listinfo;
+ pfree(mappings);
break;
}
@@ -2273,7 +2297,6 @@ static bool
equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
{
int i;
- int *mapping;
if (l1->nvalues != l2->nvalues)
return false;
@@ -2282,35 +2305,15 @@ equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
return false;
for (i = 0; i < l1->nvalues; i++)
+ {
if (list_values_cmp(key, l1->values[i], l2->values[i]))
return false;
- /*
- * Index start from one. A zero in nth slot means that partition n of
- * the first collection has not yet been mapped with a partition from
- * the second collection.
- */
- mapping = (int *) palloc0((n + 1) * sizeof(int));
- for (i = 0; i < l1->nvalues; i++)
- {
- int l1_partno = l1->indexes[i] + 1,
- l2_partno = l2->indexes[i] + 1;
-
- /* Encountered l1_partno for the first time, map to l2_partno */
- if (mapping[l1_partno] == 0)
- mapping[l1_partno] = l2_partno;
- /*
- * Maintain that the mapping does not change, otherwise report
- * inequality
- */
- else if (mapping[l1_partno] != l2_partno)
- return false;
+ /* Make sure that the indexes are "canonicalised. */
+ Assert(l1->indexes[i] == l2->indexes[i]);
}
- /* Check that nulls are accepted in mapped partitions */
- Assert(l1->has_null || l1->null_index == -1);
- Assert(l2->has_null || l2->null_index == -1);
- if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1)
+ if (l1->null_index != l2->null_index);
return false;
return true;
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-22 10:10 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-22 10:10 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Thu, Sep 22, 2016 at 1:02 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> For list partitions, the ListInfo stores the index maps for values
> i.e. the index of the partition to which the value belongs. Those
> indexes are same as the indexes in partition OIDs array and come from
> the catalogs. In case a user creates two partitioned tables with
> exactly same lists for partitions but specifies them in a different
> order, the OIDs are stored in the order specified. This means that
> index array for these tables come out different. equal_list_info()
> works around that by creating an array of mappings and checks whether
> that mapping is consistent for all values. This means we will create
> the mapping as many times as equal_list_info() is called, which is
> expected to be more than the number of time
> RelationBuildPartitionDescriptor() is called. Instead, if we
> "canonicalise" the indexes so that they come out exactly same for
> similarly partitioned tables, we build the mapping only once and
> arrange OIDs accordingly.
>
> Here's patch to do that. I have ran make check with this and it didn't
> show any failure. Please consider this to be included in your next set
> of patches.
The patch has an if condition as statement by itself
+ if (l1->null_index != l2->null_index);
return false;
There shouldn't be ';' at the end. It looks like in the tests you have
added the function always bails out before it reaches this statement.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-26 07:49 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-09-26 07:49 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi Ashutosh,
On 2016/09/22 14:42, Ashutosh Bapat wrote:
> Hi Amit,
> Following sequence of DDLs gets an error
> --
> -- multi-leveled partitions
> --
> CREATE TABLE prt1_l (a int, b int, c varchar) PARTITION BY RANGE(a);
> CREATE TABLE prt1_l_p1 PARTITION OF prt1_l FOR VALUES START (0) END
> (250) PARTITION BY RANGE (b);
> CREATE TABLE prt1_l_p1_p1 PARTITION OF prt1_l_p1 FOR VALUES START (0) END (100);
> CREATE TABLE prt1_l_p1_p2 PARTITION OF prt1_l_p1 FOR VALUES START
> (100) END (250);
> CREATE TABLE prt1_l_p2 PARTITION OF prt1_l FOR VALUES START (250) END
> (500) PARTITION BY RANGE (c);
> CREATE TABLE prt1_l_p2_p1 PARTITION OF prt1_l_p2 FOR VALUES START
> ('0250') END ('0400');
> CREATE TABLE prt1_l_p2_p2 PARTITION OF prt1_l_p2 FOR VALUES START
> ('0400') END ('0500');
> CREATE TABLE prt1_l_p3 PARTITION OF prt1_l FOR VALUES START (500) END
> (600) PARTITION BY RANGE ((b + a));
> ERROR: cannot use column or expression from ancestor partition key
>
> The last statement is trying create subpartitions by range (b + a),
> which contains a partition key from ancestor partition key but is not
> exactly same as that. In fact it contains some extra columns other
> than the ancestor partition key columns. Why do we want to prohibit
> such cases?
Per discussion [1], I am going to remove this ill-considered restriction.
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BHiwqEXAU_m%2BV%3Db-VGmsDNjoqc-Z_9KQdyPuOGbiQGzNObmVg%40ma...
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 05:27 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-27 05:27 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On 2016/09/22 19:10, Ashutosh Bapat wrote:
> On Thu, Sep 22, 2016 at 1:02 PM, Ashutosh Bapat
> <ashutosh.bapat@enterprisedb.com> wrote:
>> For list partitions, the ListInfo stores the index maps for values
>> i.e. the index of the partition to which the value belongs. Those
>> indexes are same as the indexes in partition OIDs array and come from
>> the catalogs. In case a user creates two partitioned tables with
>> exactly same lists for partitions but specifies them in a different
>> order, the OIDs are stored in the order specified. This means that
>> index array for these tables come out different. equal_list_info()
>> works around that by creating an array of mappings and checks whether
>> that mapping is consistent for all values. This means we will create
>> the mapping as many times as equal_list_info() is called, which is
>> expected to be more than the number of time
>> RelationBuildPartitionDescriptor() is called. Instead, if we
>> "canonicalise" the indexes so that they come out exactly same for
>> similarly partitioned tables, we build the mapping only once and
>> arrange OIDs accordingly.
>>
>> Here's patch to do that. I have ran make check with this and it didn't
>> show any failure. Please consider this to be included in your next set
>> of patches.
Thanks. It seems like this will save quite a few cycles for future users
of equal_list_info(). I will incorporate it into may patch.
With this patch, the mapping is created *only once* during
RelationBuildPartitionDesc() to assign canonical indexes to individual
list values. The partition OID array will also be rearranged such that
using the new (canonical) index instead of the old
catalog-scan-order-based index will retrieve the correct partition for
that value.
By the way, I fixed one thinko in your patch as follows:
- result->oids[i] = oids[mapping[i]];
+ result->oids[mapping[i]] = oids[i];
That is, copy an OID at a given position in the original array to a
*mapped position* in the result array (instead of the other way around).
> The patch has an if condition as statement by itself
> + if (l1->null_index != l2->null_index);
> return false;
>
> There shouldn't be ';' at the end. It looks like in the tests you have
> added the function always bails out before it reaches this statement.
There is no user of equal_list_info() such that the above bug would have
caused a regression test failure. Maybe a segfault crash (due to dangling
pointer into partition descriptor's listinfo after the above function
would unintentionally return false to equalPartitionDescs() which is in
turn called by RelationClearRelation()).
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 06:44 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-27 06:44 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
>
> With this patch, the mapping is created *only once* during
> RelationBuildPartitionDesc() to assign canonical indexes to individual
> list values. The partition OID array will also be rearranged such that
> using the new (canonical) index instead of the old
> catalog-scan-order-based index will retrieve the correct partition for
> that value.
>
> By the way, I fixed one thinko in your patch as follows:
>
> - result->oids[i] = oids[mapping[i]];
> + result->oids[mapping[i]] = oids[i];
While I can not spot any problem with this logic, when I make that
change and run partition_join testcase in my patch, it fails because
wrong partitions are matched for partition-wise join of list
partitions. In that patch, RelOptInfo of partitions are saved in
RelOptInfo of the parent by matching their OIDs. They are saved in the
same order as corresponding OIDs. Partition-wise join simply joins the
RelOptInfos at the same positions from both the parent RelOptInfos. I
can not spot an error in this logic too.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 08:39 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-27 08:39 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Sorry about the delay in replying.
On 2016/09/15 21:58, Ashutosh Bapat wrote:
> Hi Amit,
>
> It looks like there is some problem while creating paramterized paths
> for multi-level partitioned tables. Here's a longish testcase
>
> [ ... ]
>
> Please check if you are able to reproduce these errors in your
> repository. I made sure that I cleaned up all partition-wise join code
> before testing this, but ... .
Thanks for the test case. I can reproduce the same.
> I tried to debug the problem somewhat. In set_append_rel_pathlist(),
> it finds that at least one child has a parameterized path as the
> cheapest path, so it doesn't create an unparameterized path for append
> rel. At the same time there is a parameterization common to all the
> children, so it doesn't create any path. There seem to be two problems
> here
> 1. The children from second level onwards may not be getting
> parameterized for lateral references. That seems unlikely but
> possible.
> 2. Reparameterization should have corrected this, but
> reparameterize_path() does not support AppendPaths.
Hmm, 0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patch is
certainly to be blamed here; if I revert the patch, the problem goes away.
Based on 2 above, I attempted to add logic for AppendPath in
reparameterize_path() as in the attached. It fixes the reported problem
and does not break any regression tests. If it's sane to do things this
way, I will incorporate the attached into patch 0005 mentioned above.
Thoughts?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] reparameterize-append-path.patch (4.8K, ../../45d2f224-634d-8e50-48c0-8d649a91fcd6@lab.ntt.co.jp/2-reparameterize-append-path.patch)
download | inline diff:
commit 86c8fc2c268d17fb598bf6879cfef2b2a2f42417
Author: amit <amitlangote09@gmail.com>
Date: Tue Sep 27 16:02:54 2016 +0900
Handle AppendPath in reparameterize_path().
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 2a49639..750f0ea 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1570,6 +1570,40 @@ cost_sort(Path *path, PlannerInfo *root,
}
/*
+ * cost_append
+ * Determines and returns the cost of a Append node
+ *
+ * Compute rows and costs as sums of subplan rows and costs. We charge
+ * nothing extra for the Append itself, which perhaps is too optimistic,
+ * but since it doesn't do any selection or projection, it is a pretty
+ * cheap node.
+ */
+void
+cost_append(AppendPath *apath, Relids required_outer)
+{
+ ListCell *l;
+
+ apath->path.rows = 0;
+ apath->path.startup_cost = 0;
+ apath->path.total_cost = 0;
+ foreach(l, apath->subpaths)
+ {
+ Path *subpath = (Path *) lfirst(l);
+
+ apath->path.rows += subpath->rows;
+
+ if (l == list_head(apath->subpaths)) /* first node? */
+ apath->path.startup_cost = subpath->startup_cost;
+ apath->path.total_cost += subpath->total_cost;
+ apath->path.parallel_safe = apath->path.parallel_safe &&
+ subpath->parallel_safe;
+
+ /* All child paths must have same parameterization */
+ Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
+ }
+}
+
+/*
* cost_merge_append
* Determines and returns the cost of a MergeAppend node.
*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index abb7507..bf8fb55 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1206,7 +1206,6 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
int parallel_workers)
{
AppendPath *pathnode = makeNode(AppendPath);
- ListCell *l;
pathnode->path.pathtype = T_Append;
pathnode->path.parent = rel;
@@ -1220,32 +1219,7 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
* unsorted */
pathnode->subpaths = subpaths;
- /*
- * We don't bother with inventing a cost_append(), but just do it here.
- *
- * Compute rows and costs as sums of subplan rows and costs. We charge
- * nothing extra for the Append itself, which perhaps is too optimistic,
- * but since it doesn't do any selection or projection, it is a pretty
- * cheap node.
- */
- pathnode->path.rows = 0;
- pathnode->path.startup_cost = 0;
- pathnode->path.total_cost = 0;
- foreach(l, subpaths)
- {
- Path *subpath = (Path *) lfirst(l);
-
- pathnode->path.rows += subpath->rows;
-
- if (l == list_head(subpaths)) /* first node? */
- pathnode->path.startup_cost = subpath->startup_cost;
- pathnode->path.total_cost += subpath->total_cost;
- pathnode->path.parallel_safe = pathnode->path.parallel_safe &&
- subpath->parallel_safe;
-
- /* All child paths must have same parameterization */
- Assert(bms_equal(PATH_REQ_OUTER(subpath), required_outer));
- }
+ cost_append(pathnode, required_outer);
return pathnode;
}
@@ -3204,6 +3178,41 @@ reparameterize_path(PlannerInfo *root, Path *path,
spath->path.pathkeys,
required_outer);
}
+ case T_Append:
+ {
+ AppendPath *apath = (AppendPath *) path;
+ AppendPath *newpath;
+ List *new_subpaths = NIL;
+ ListCell *lc;
+
+ foreach(lc, apath->subpaths)
+ {
+ Path *subpath = lfirst(lc);
+ Path *new_subpath;
+
+ new_subpath = reparameterize_path(root, subpath,
+ required_outer,
+ loop_count);
+ if (new_subpath)
+ new_subpaths = lappend(new_subpaths, new_subpath);
+ else
+ return NULL;
+ }
+
+ /*
+ * Like IndexPath, we flat-copy the path node, and revise
+ * its param_info, and redo the cost estimate (costs of
+ * subpaths may have changed due to parameterization).
+ */
+ newpath = makeNode(AppendPath);
+ memcpy(newpath, apath, sizeof(AppendPath));
+ newpath->path.param_info =
+ get_appendrel_parampathinfo(rel, required_outer);
+ newpath->subpaths = new_subpaths;
+ cost_append(newpath, required_outer);
+
+ return (Path *) newpath;
+ }
default:
break;
}
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 2a4df2f..ec1d6f2 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -98,6 +98,7 @@ extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
double limit_tuples);
+extern void cost_append(AppendPath *apath, Relids required_outer);
extern void cost_merge_append(Path *path, PlannerInfo *root,
List *pathkeys, int n_streams,
Cost input_startup_cost, Cost input_total_cost,
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 09:09 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-27 09:09 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
>>
>> Please check if you are able to reproduce these errors in your
>> repository. I made sure that I cleaned up all partition-wise join code
>> before testing this, but ... .
>
> Thanks for the test case. I can reproduce the same.
>
>> I tried to debug the problem somewhat. In set_append_rel_pathlist(),
>> it finds that at least one child has a parameterized path as the
>> cheapest path, so it doesn't create an unparameterized path for append
>> rel. At the same time there is a parameterization common to all the
>> children, so it doesn't create any path. There seem to be two problems
>> here
>> 1. The children from second level onwards may not be getting
>> parameterized for lateral references. That seems unlikely but
>> possible.
Did you check this? We may be missing on creating index scan paths
with parameterization. If we fix this, we don't need to
re-parameterize Append.
>> 2. Reparameterization should have corrected this, but
>> reparameterize_path() does not support AppendPaths.
>
> Hmm, 0005-Refactor-optimizer-s-inheritance-set-expansion-code-5.patch is
> certainly to be blamed here; if I revert the patch, the problem goes away.
>
> Based on 2 above, I attempted to add logic for AppendPath in
> reparameterize_path() as in the attached. It fixes the reported problem
> and does not break any regression tests. If it's sane to do things this
> way, I will incorporate the attached into patch 0005 mentioned above.
> Thoughts?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 09:16 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-27 09:16 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On 2016/09/27 15:44, Ashutosh Bapat wrote:
>> By the way, I fixed one thinko in your patch as follows:
>>
>> - result->oids[i] = oids[mapping[i]];
>> + result->oids[mapping[i]] = oids[i];
>
> While I can not spot any problem with this logic, when I make that
> change and run partition_join testcase in my patch, it fails because
> wrong partitions are matched for partition-wise join of list
> partitions. In that patch, RelOptInfo of partitions are saved in
> RelOptInfo of the parent by matching their OIDs. They are saved in the
> same order as corresponding OIDs. Partition-wise join simply joins the
> RelOptInfos at the same positions from both the parent RelOptInfos. I
> can not spot an error in this logic too.
OTOH, using the original logic makes tuple routing put tuples into the
wrong partitions. When debugging why that was happening I discovered this
and hence the proposed change.
You mean that partition RelOptInfo's are placed using the canonical
ordering of OIDs instead of catalog-scan-driven order, right? If that's
true, then there is no possibility of wrong pairing happening, even with
the new ordering of OIDs in the partition descriptor (ie, the ordering
that would be produced by my proposed method above).
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 09:18 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-09-27 09:18 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Tue, Sep 27, 2016 at 2:46 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/09/27 15:44, Ashutosh Bapat wrote:
>>> By the way, I fixed one thinko in your patch as follows:
>>>
>>> - result->oids[i] = oids[mapping[i]];
>>> + result->oids[mapping[i]] = oids[i];
>>
>> While I can not spot any problem with this logic, when I make that
>> change and run partition_join testcase in my patch, it fails because
>> wrong partitions are matched for partition-wise join of list
>> partitions. In that patch, RelOptInfo of partitions are saved in
>> RelOptInfo of the parent by matching their OIDs. They are saved in the
>> same order as corresponding OIDs. Partition-wise join simply joins the
>> RelOptInfos at the same positions from both the parent RelOptInfos. I
>> can not spot an error in this logic too.
>
> OTOH, using the original logic makes tuple routing put tuples into the
> wrong partitions. When debugging why that was happening I discovered this
> and hence the proposed change.
>
> You mean that partition RelOptInfo's are placed using the canonical
> ordering of OIDs instead of catalog-scan-driven order, right? If that's
> true, then there is no possibility of wrong pairing happening, even with
> the new ordering of OIDs in the partition descriptor (ie, the ordering
> that would be produced by my proposed method above).
right! I don't know what's wrong, will debug my changes.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-27 11:19 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-09-27 11:19 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On 2016/09/27 18:09, Ashutosh Bapat wrote:
>>> I tried to debug the problem somewhat. In set_append_rel_pathlist(),
>>> it finds that at least one child has a parameterized path as the
>>> cheapest path, so it doesn't create an unparameterized path for append
>>> rel. At the same time there is a parameterization common to all the
>>> children, so it doesn't create any path. There seem to be two problems
>>> here
>>> 1. The children from second level onwards may not be getting
>>> parameterized for lateral references. That seems unlikely but
>>> possible.
>
> Did you check this? We may be missing on creating index scan paths
> with parameterization. If we fix this, we don't need to
> re-parameterize Append.
You're right. How about the attached patch that fixes the problem along
these lines? The problem seems to be that multi-level inheritance sets
(partitioned tables) are not handled in create_lateral_join_info(), which
means that lateral_relids and lateral_referencers of the root relation are
not being propagated to the partitions below level 1.
I'm getting concerned about one thing though - for a given *regular*
inheritance set, the root->append_rel_list would be scanned only once; But
for a *partitioned table* inheritance set, it would be scanned for every
partitioned table in the set (ie, the root table and internal partitions).
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] multi-level-inh-lateral-join-info.patch (1.3K, ../../2896956a-8af0-3f91-a3bc-1e5225ae7ed2@lab.ntt.co.jp/2-multi-level-inh-lateral-join-info.patch)
download | inline diff:
commit d69aeabcfcb58f349602a1b7392c611045c37465
Author: amit <amitlangote09@gmail.com>
Date: Tue Sep 27 20:01:44 2016 +0900
Consider multi-level partitioned tables in create_lateral_join_info().
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..74734f2 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -623,8 +624,19 @@ create_lateral_join_info(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *rte = root->simple_rte_array[rti];
- if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+ if (brel == NULL)
+ continue;
+
+ /*
+ * If an "other rel" RTE is a "partitioned table", we must propagate
+ * the lateral info inherited from the parent to its children. That's
+ * because they are not linked directly with the parent via
+ * AppendRelInfo's (see expand_inherited_rte_internal()).
+ */
+ if (brel->reloptkind != RELOPT_BASEREL &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
continue;
if (root->simple_rte_array[rti]->inh)
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-29 12:09 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-09-29 12:09 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Thanks a lot for the review and sorry about the delay in replying.
Combining responses to two emails.
On 2016/09/20 5:06, Robert Haas wrote:
> On Mon, Aug 15, 2016 at 7:21 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> + if (partexprs)
>>> + recordDependencyOnSingleRelExpr(&myself,
>>> + (Node *) partexprs,
>>> + RelationGetRelid(rel),
>>> + DEPENDENCY_NORMAL,
>>> + DEPENDENCY_IGNORE);
>>>
>>> I don't think introducing a new DEPENDENCY_IGNORE type is a good idea
>>> here. Instead, you could just add an additional Boolean argument to
>>> recordDependencyOnSingleRelExpr. That seems less likely to create
>>> bugs in unrelated portions of the code.
>>
>> I did consider a Boolean argument instead of a new DependencyType value,
>> however it felt a bit strange to pass a valid value for the fifth argument
>> (self_behavior) and then ask using a separate parameter that it (a
>> self-dependency) is to be ignored. By the way, no pg_depend entry is
>> created on such a call, so the effect of the new type's usage seems
>> localized to me. Thoughts?
>
> I think that's not a very plausible argument. If you add a fifth
> argument to that function, then only that function needs to know about
> the possibility of ignoring self-dependencies. If you add a new
> dependency type, then everything that knows about DependencyType needs
> to know about them. That's got to be a much larger surface area for
> bugs. Also, if you look around a bit, I believe you will find other
> examples of cases where one argument is used only for certain values
> of some other argument. That's not a novel design pattern.
I removed DEPENDENCY_IGNORE. Does the following look good or am I still
missing something?
@@ -83,7 +78,6 @@ typedef enum DependencyType
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
DEPENDENCY_PIN = 'p',
- DEPENDENCY_IGNORE = 'g'
@@ -194,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress
*depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
@@ -1450,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress
*depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
@@ -138,7 +138,7 @@ StorePartitionKey(Relation rel,
(Node *) partexprbin,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
- DEPENDENCY_IGNORE);
+ DEPENDENCY_NORMAL, true);
> Re-reviewing 0001.
>
> + <entry><structfield>partexprs</structfield></entry>
> + <entry><type>pg_node_tree</type></entry>
>
> This documentation doesn't match pg_partition_table.h, which has
> partexprsrc and partexprbin. I don't understand why it's a good idea
> to have both, and there seem to be no comments or documentation
> supporting that choice anywhere.
Removed partexprsrc, I realized the design whereby it was required to be
stored in the catalog was a dubious one after all.
> + 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 expression must have an associated
> + btree operator class or one must be specified along with the column
> + or the expression.
>
> Both of the sentences in this paragraph that do not begin with "the"
> need to begin with "the". (In my experience, it's generally a feature
> of English as spoken in India that connecting words like "the" and "a"
> are sometimes left out where non-Indian speakers of English would
> include them, so it would be good to watch out for this issue in
> general.)
Thanks for the tip, will try to be careful (rules about a/an/the's can be
too subtle for me sometimes, so any help is much appreciated).
> Also, I think this should be rephrased a bit to be more clear about
> how the partitioning key works, like this: The optional
> <literal>PARTITION BY</literal> clause specifies a method of
> partitioning the table. The table thus created is called a
> <firstterm>partitioned</firstterm> table. The parenthesized list of
> expressions forms the <firsttem>partitioning key</firstterm> for the
> table. When using range partitioning, the partioning key can include
> multiple columns or expressions, but for list partitioning, the
> partitioning key must consist of a single column or expression. If no
> btree operator class is specified when creating a partitioned table,
> the default btree operator class for the datatype will be used. If
> there is none, an error will be reported.
Revised text along these lines.
> + case RELKIND_PARTITIONED_TABLE:
> options = heap_reloptions(classForm->relkind, datum, false);
>
> Why? None of the reloptions that pertain to heap seem relevant to a
> relkind without storage.
>
> But, ah, do partitioned tables have storage? I mean, do we end up
> with an empty file, or no relfilenode at all? Can I CLUSTER, VACUUM,
> etc. a partitioned table? It would seem cleaner for the parent to
> have no relfilenode at all, but I'm guessing we might need some more
> changes for that to work out.
Things were that way initially, that is, the parent relations had no
relfilenode. I abandoned that project however. The storage-less parent
thing seemed pretty daunting to me to handle right away. For example,
optimizer and the executor code needed to be taught about the parent rel
appendrel member that used to be included as part of the result of
scanning the inheritance set but was no longer.
That said, there are still diffs in the patch resulting from simply
informing various sites in the code that there is a new relkind
RELKIND_PARTITIONED_TABLE, most of the sites giving it the same treatment
as RELKIND_RELATION. Except maybe that the new relkind serves as a
convenient shorthand for specifying to various modules that the relation
is a partitioned table that is in some regards to be treated differently
from the ordinary tables.
> + pg_collation.h pg_range.h pg_transform.h pg_partitioned_table.h\
>
> Whitespace. Also, here and elsewhere, how about using alphabetical
> order, or anyway preserving it insofar as the existing list is
> alphabetized?
Fixed.
> + /* Remove NO INHERIT flag if rel is a partitioned table */
> + if (is_no_inherit &&
> + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
> + errmsg("cannot add NO INHERIT constraint to
> partitioned table \"%s\"",
> + RelationGetRelationName(rel))));
>
> The code and the comment disagree. I think the code is right and the
> comment should be adjusted to say something like /* Partitioned tables
> do not have storage, so a NO INHERIT constraint makes no sense. */
Rewrote the comment.
>
> + * IDENTIFICATION
> + * src/backend/utils/misc/partition.c
>
> Wrong.
This file is no longer part of the 0001 patch since I moved the file's
only function to relcache.c as mentioned below. Fixed in the later patch
nonetheless.
>
> +} KeyTypeCollInfo;
>
> I don't like this type name very much. Can we get "Part" in there someplace?
>
> It doesn't seem to be very well-designed, either. The number of
> entries in each array is determined by the partnatts flag in
> PartitionKeyData, which has also got various other arrays whose
> lengths are determined by partnatts. Why do we have some arrays in
> one structure and some arrays in another structure? Would it hurt
> anything to merge everything into one structure? Or could
> PartitionKeyData include a field of type KeyTypeCollInfo rather than
> KeyTypeCollInfo *, saving one pointer reference every place we access
> this data?
I followed your advice to just move the typ* arrays into the
PartitionKeyData struct.
> + /* Allocate in the supposedly short-lived working context */
>
> Why supposedly?
When writing the code, I was thinking of the following header comment that
I copied from somewhere:
* 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.
Removed the comment as it is not very illuminating anymore.
> + datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs,
> + RelationGetDescr(catalog),
> + &isnull);
>
> Isn't the point of putting the fixed-length fields first that we can
> use GETSTRUCT() here? And even for partattrs as the first
> variable-length thing?
Right, fixed.
>
> + /*
> + * 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.
> + */
>
> I'm a bit confused by this, because I would think this processing
> ought to have been done before storing anything in the system
> catalogs. I don't see why it should be necessary to do it again after
> pulling data back out of the system catalogs.
The pattern matches what's done for other expressions that optimizer deals
with, such as CHECK, index key, and index predicate expressions.
> + Value *str = lfirst(partexprsrc_item);
> + key->partcolnames[i] = pstrdup(str->val.str);
>
> Should have a blank line in between.
Fixed.
> +/*
> + * Partition key information inquiry functions
> + */
> +int
> +get_partition_key_strategy(PartitionKey key)
> +{
> + return key->strategy;
> +}
> +
> [ ... ]
> +
> +int32
> +get_partition_col_typmod(PartitionKey key, int col)
> +{
> + return key->tcinfo->typmod[col];
> +}
>
> If we're going to add notation for this, I think we should use macros
> (or static inline functions defined in the header file). Doing it
> this way adds more cycles for no benefit.
OK, done using the static inline functions defined in the header file way.
> + newkey->partattrs = (AttrNumber *)
> + palloc0(newkey->partnatts * sizeof(AttrNumber));
> + memcpy(newkey->partattrs, fromkey->partattrs,
> + newkey->partnatts * sizeof(AttrNumber));
>
> It's wasteful to use palloc0 if you're immediately going to overwrite
> every byte in the array. Use regular palloc instead.
Done.
> + /* Only this can ever be NULL */
> + if (!partexprbinDatum)
> + {
> + nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
> + nulls[Anum_pg_partitioned_table_partexprsrc - 1] = true;
> + }
>
> How can it be valid to have no partitioning expressions?
Keys that are simply column names are resolved to attnums and stored
likewise. If some key is an expression, then corresponding attnum is 0
and the expression itself is added to the list that gets stored into
partexprbin. It is doing the same thing as index expressions.
> + /* Tell world about the key */
> + CacheInvalidateRelcache(rel);
>
> Is this really needed? Isn't the caller going to do something similar
> pretty soon?
Oops, I removed that in one of the later patches but shouldn't be there in
the first place. Fixed.
> + heap_freetuple(tuple);
>
> Probably useless - might as well let the context reset clean it up.
Removed.
> + simple_heap_delete(rel, &tuple->t_self);
> +
> + /* Update the indexes on pg_partitioned_table */
> + CatalogUpdateIndexes(rel, tuple);
>
> You don't need CatalogUpdateIndexes() after a delete, only after an
> insert or update.
Ah right, removed.
>
> + if (classform->relkind != relkind &&
> + (relkind == RELKIND_RELATION &&
> + classform->relkind != RELKIND_PARTITIONED_TABLE))
>
> That's broken. Note that all of the conditions are joined using &&,
> so if any one of them fails then we won't throw an error. In
> particular, it's no longer possible to throw an error when relkind is
> not RELKIND_RELATION.
You are right. I guess it would have to be the following:
+ if ((classform->relkind != relkind &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE) ||
+ (classform->relkind == RELKIND_PARTITIONED_TABLE &&
+ relkind != RELKIND_RELATION))
Such hackishness could not be helped because we have a separate DROP
command for every distinct relkind, except we overload DROP TABLE for both
regular and partitioned tables.
> +/* 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 *var = (Var *) node;
> + char *varattname = get_attname(context->relid, var->varattno);
> +
> + if (!strcmp(varattname, context->attname))
> + return true;
> + }
> +
> + return expression_tree_walker(node, find_attr_reference_walker, context);
> +}
>
> Hrm. The comment says we're matching on attnum, but the code says
> we're matching on attname. is_partition_attr() has the same confusion
> between comments and code. Maybe instead of this whole approach it
> would be better to use pull_varattnos(), then get_attnum() to find the
> attribute number for the one you want, then bms_is_member().
I like the idea to use pull_varattnos(), so done that way.
> +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);
>
> Why isn't this logic being invoked from transformCreateStmt()? Then
> we could use the actual parseState for the query instead of a fake
> one.
Because we need an open relation for it to work, which in this case there
won't be until after we have performed heap_create_with_catalog() in
DefineRelation(). Mainly because we need to perform transformExpr() on
expressions. That's similar to how cookConstraint() on the new CHECK
constraints cannot be performed earlier. Am I missing something?
> + if (IsA(expr, CollateExpr))
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
> + errmsg("cannot use COLLATE in partition key
> expression")));
>
> I assume there is a good reason for this seemingly-arbitrary
> restriction, but there's no comment saying what it is. One thing
> that's odd is that this will only prohibit a CollateExpr at the top
> level, not in some more-deeply nested position. That seems
> suspicious.
Hmm, I guess it wouldn't hurt to just leave any COLLATE clauses as it is -
just remove the above code. Of course, we must then record the collation
in the catalog alongside other user-specified information such as operator
class. Currently, if the key is a simple column we use its attcollation
and if it's an expression then we use its exprCollation().
When I first wrote it, I wasn't sure what the implications of explicit
collations would be for partitioning. There are two places where it comes
into play: a) when comparing partition key values using the btree compare
function b) embedded as varcollid and inputcollid in implicitly generated
check constraints for partitions. In case of the latter, any mismatch
with query-specified collation causes constraint exclusion proof to be
canceled. When it's default collations everywhere, the chances of that
sort of thing happening are less. If we support user-specified collations
on keys, then things will get a little bit more involved.
Thoughts?
> + /*
> + * User wrote "(column)" or "(column COLLATE something)".
> + * Treat it like simple attribute anyway.
> + */
>
> Evidently, the user did not do that, because you just prohibited the
> second one of those.
Holds true now that I have removed the prohibition.
> + 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")));
>
> Do these checks parallel what we do for CHECK constraints? It might
> be good to apply about the same level of rigor in both cases.
Both of these checks are not done for CHECK constraints. The only check
performed on them in cookConstraint() checks whether the expression is of
boolean type.
However in the present case, this is just one side of a whole partition
constraint (the other piece being individual partition's bound value), so
should be treated a bit differently from the CHECK constraints. I modeled
this on ComputeIndexAttrs() checks viz. the following:
/*
* An expression using mutable functions is probably wrong,
* since if you aren't going to get the same result for the
* same data every time, it's not clear what the index entries
* mean at all.
*/
if (CheckMutability((Expr *) expr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("functions in index expression must be marked IMMUTABLE")));
Likewise if a partition key expression contained mutable functions, same
input row could be mapped to different partitions based on the result of
expression computed using the input values. So, it seems prudent to
enforce immutability unlike CHECK constraints. Am I missing something?
>
> + exprsrc = deparse_expression(expr,
> + deparse_context_for(RelationGetRelationName(rel),
> + RelationGetRelid(rel)),
> + false, false);
>
> Why bother? The output of this doesn't seem like a useful thing to
> store. The fact that we've done similar things elsewhere doesn't make
> it a good idea. I think we did it in other cases because we used to
> be dumber than we are now.
Deparsed expressions are no longer stored in the catalog.
> + (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.")));
>
> The hint is not really accurate, because the type may well have a
> btree operator class. Just not a default one.
Changed to: "You must specify a btree operator class or define a default
btree operator class for the data type."
> + char relkind = ((CreateStmt *)
> stmt)->partby != NULL
> + ? RELKIND_PARTITIONED_TABLE
> + : RELKIND_RELATION;
>
> Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
> NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
> RELKIND_PARTITION_TABLE; }
Done. By the way, I made the ereport say the following: "unexpected
relkind value passed to DefineRelation", did you intend it to say
something else?
>
> + RelationBuildPartitionKey(relation);
>
> I wonder if RelationBuildPartitionKey should really be in relcache.c.
> What do we do in similar cases?
There a number of RelationBuild* functions that RelationBuildDesc calls
that are reside outside relcache.c in respective modules - trigger.c
(BuildTrigger), policy.c (BuildRowSecurity), whereas some others such as
RelationInitIndexAccessInfo() are housed in relcache.c.
BuildPartitionKey used to be in relcache.c and can be moved back there. So
did.
>
> +} PartitionBy;
>
> Maybe PartitionSpec?
A later patch uses PartitionListSpec and PartitionRangeSpec as parse nodes
for partition bounds. How about call PartitionBy PartitionKeySpec and the
bound nodes just mentioned PartitionBoundList and PartitionBoundRange,
respectively?
Attached revised patches. In addition to addressing comments in this
email (which addressed only the patch 0001), number of other fixes [1] and
other miscellaneous improvements [2] have been included.
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/2896956a-8af0-3f91-a3bc-1e5225ae7ed2@lab.ntt.co.jp
[2]
https://www.postgresql.org/message-id/98d6c7e7-4919-4970-a158-49f740101812%40lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-6.patch (111.9K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-6.patch)
download | inline diff:
From 891c823431ccfeac4e32bf12be20f1d403f3a5e3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
1. In addition to a catalog for storing the partition key information,
this commit also adds a new relkind to pg_class.h. A new dependency type
DEPENDENCY_IGNORE is added for callers to be able to ask the dependency
subsystem to ignore self-dependencies that arise when storing dependencies
on objects mentioned in partition key expressions.
2. Add PARTITION BY clause to CREATE TABLE. Tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 102 +++++++-
doc/src/sgml/ref/create_table.sgml | 56 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 30 ++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/catalog/pg_partitioned_table.c | 153 ++++++++++
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 | 366 ++++++++++++++++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 33 +++
src/backend/nodes/equalfuncs.c | 28 ++
src/backend/nodes/outfuncs.c | 26 ++
src/backend/parser/gram.y | 110 ++++++--
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 69 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 230 +++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 5 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 67 +++++
src/include/catalog/pg_partitioned_table_fn.h | 28 ++
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 | 75 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 154 +++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 132 +++++++++
53 files changed, 1841 insertions(+), 51 deletions(-)
create mode 100644 src/backend/catalog/pg_partitioned_table.c
create mode 100644 src/include/catalog/pg_partitioned_table.h
create mode 100644 src/include/catalog/pg_partitioned_table_fn.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..9cc7eed 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores information
+ about the partition key of tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 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>partexprbin</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>partattrs</>. 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..b6f1f6e 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,40 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firsttem>partitioning key</firstterm>
+ for the table. When using range partitioning, the partioning key can
+ include multiple columns or expressions, but for list partitioning, the
+ partitioning key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </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 or expressions in the partition key and partition
+ bound constraints associated with the individual partitions.
+ </para>
+
+ <para>
+ Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+ KEY constraints; however, you can define these constraints on individual
+ data partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1405,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..08b43ad 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_table.o pg_type.o storage.o toasting.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_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..d54b20a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,7 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2043,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c
new file mode 100644
index 0000000..907101f
--- /dev/null
+++ b/src/backend/catalog/pg_partitioned_table.c
@@ -0,0 +1,153 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.c
+ * Routines to support manipulation of the pg_partitioned_table relation
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_partitioned_table.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
+#include "catalog/pg_partitioned_table_fn.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * StorePartitionKey
+ * Store the partition key information of rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprbinDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /* Copy the partition key, opclass info into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprbin)
+ {
+ char *exprbinString;
+
+ exprbinString = nodeToString(partexprbin);
+ partexprbinDatum = CStringGetTextDatum(exprbinString);
+ pfree(exprbinString);
+ }
+ else
+ partexprbinDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprbinDatum)
+ nulls[Anum_pg_partitioned_table_partexprbin - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partexprbin - 1] = partexprbinDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Store dependencies on anything mentioned in the key expressions.
+ * However, ignore the column references which causes self-dependencies
+ * to be created that are undesirable. That is done by asking the
+ * dependency-tracking sub-system to ignore any such dependencies.
+ */
+ if (partexprbin)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprbin,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL, true);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 432b0ca..be3fbc9 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1736,6 +1736,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..874b320 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..bdeafdc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -39,6 +39,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table_fn.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -65,6 +66,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +218,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +441,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, Oid *partopclass);
/* ----------------------------------------------------------------
@@ -492,6 +504,15 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("unexpected relkind value passed to DefineRelation")));
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +617,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -697,6 +719,23 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition key, if any */
+ if (stmt->partspec)
+ {
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprbin = NIL;
+
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprbin, partopclass);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, stmt->partspec->strategy, partnatts,
+ partattrs, partexprbin, partopclass);
+ }
+
/*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
@@ -955,7 +994,17 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if ((classform->relkind != relkind &&
+ classform->relkind != RELKIND_PARTITIONED_TABLE) ||
+ (classform->relkind == RELKIND_PARTITIONED_TABLE &&
+ relkind != RELKIND_RELATION))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1342,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1571,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2219,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4349,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +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_TABLE ||
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_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5753,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_key_natts(key);
+ partexprs = get_partition_key_exprs(key);
+
+ partexpr_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if(partattno != 0)
+ {
+ if (is_expr)
+ *is_expr = false;
+ if (attnum == partattno)
+ return true;
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexpr_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ if (is_expr)
+ *is_expr = true;
+
+ /* Find all attributes referenced */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexpr_item = lnext(partexpr_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno == attnum)
+ return true;
+ }
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5829,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 +5874,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 +6405,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8006,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7892,6 +8037,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7907,7 +8065,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8934,6 +9093,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9556,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9979,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10179,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10068,6 +10235,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11446,6 +11620,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12070,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12224,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12236,181 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->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, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears 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);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprbin, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprbin = lappend(*partexprbin, expr);
+
+ /*
+ * 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 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")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for an partition key expression.
+ */
+ }
+ }
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..a295162 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4174,6 +4175,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ 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
* ****************************************************************
@@ -5088,6 +5115,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..fa5037b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *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
*/
@@ -3387,6 +3409,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ae86954..f0b63f1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_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 -
@@ -3851,6 +3872,11 @@ outNode(StringInfo str, const void *obj)
break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..a2e99a6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,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 <partspec> PartitionSpec OptPartitionSpec
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13782,6 +13855,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..7f496ea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..d1de990 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..c32edb7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partspec->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +608,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +623,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +646,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +717,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +733,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +749,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +769,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..57c2933 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,214 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * 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.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ ListCell *partexprbin_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catlog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprbin,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ 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->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprbin_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprbin_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprbin_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprbin_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1263,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2264,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3207,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5035,6 +5261,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;
/*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..37c705f
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprbin; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 6
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partexprbin 6
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h
new file mode 100644
index 0000000..fb003ef
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table_fn.h
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table_fn.h
+ * prototypes for functions in catalog/pg_partitioned_table.c
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partitioned_table_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_FN_H
+#define PG_PARTITIONED_TABLE_FN_H
+
+#include "utils/relcache.h"
+
+/* pg_partitioned_table catalog functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprbin,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
+#endif /* PG_PARTITIONED_TABLE_FN_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..373b8ed 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,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;
+
+/*
+ * PartitionSpec - 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 PartitionSpec
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
/****************************************************************************
* Nodes for a Query tree
@@ -1753,6 +1787,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/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 6633586..99f68c7 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..c775ca4 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,30 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ List *partexprs; /* partition key expressions, if any */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Type and collation information of partition attributes */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +118,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +559,54 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_key_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_key_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_key_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..1394362 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,157 @@ 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: set-returning functions are not allowed in partition key expression
+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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use 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
+-- 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 a btree operator class or define a default btree operator class for the data 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 a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..d383f58 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,135 @@ 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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+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);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-6.patch (22.5K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-6.patch)
download | inline diff:
From 971432b577d1caa75566926f63d01d68ed4d1f8f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 140 ++++++++++++++++++++++++++++
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, 260 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.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,144 @@ 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 relid = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprbin))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprbin, &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);
+
+ 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 51b8a1a..854dddc 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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,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");
@@ -5296,7 +5301,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 "
@@ -5308,7 +5314,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,
@@ -5322,7 +5328,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,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)
{
@@ -5954,6 +5962,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 */
@@ -5998,7 +6007,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: 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 d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-6.patch (205.8K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-6.patch)
download | inline diff:
From ed124df5caf21d97c785b1d3d461783bac6eda6b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 33 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1772 ++++++++++++++++++++++++++++
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/lockcmds.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 774 ++++++++++--
src/backend/nodes/copyfuncs.c | 48 +
src/backend/nodes/equalfuncs.c | 42 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 33 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_agg.c | 1 -
src/backend/parser/parse_utilcmd.c | 366 ++++++-
src/backend/utils/cache/relcache.c | 105 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 59 +
src/include/catalog/pg_class.h | 22 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 42 +-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 2 +-
src/include/utils/rel.h | 9 +
src/test/regress/expected/alter_table.out | 219 ++++
src/test/regress/expected/create_table.out | 188 +++
src/test/regress/sql/alter_table.sql | 192 +++
src/test/regress/sql/create_table.sql | 139 +++
34 files changed, 4399 insertions(+), 163 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9cc7eed..fe701f6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..a4fff4f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1043,10 +1122,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1054,7 +1135,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1233,6 +1315,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 b6f1f6e..45dc268 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>
@@ -1423,7 +1493,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 08b43ad..291aa64 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d54b20a..80f64a6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2052,6 +2064,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2478,7 +2497,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..6af629c
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1772 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; maps
+ * element-by-element with the values array */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+ int nvalues;
+ Datum *values;
+ bool has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+ Datum value;
+ int index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+ Oid oid;
+ PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n,
+ int *with);
+
+/* Support get_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+ PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+ bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ ListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ RangePartition **range_parts;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ oids[i++] = partrelid;
+ }
+
+ /* Convert from node to a internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionList **list;
+ int j;
+
+ list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+ i = 0;
+ all_values_count = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundList *list_spec;
+
+ Assert(IsA(lfirst(cell), PartitionBoundList));
+ list_spec = (PartitionBoundList *) lfirst(cell);
+
+ list[i] = make_list_from_spec(key, list_spec);
+
+ if (list[i]->has_null)
+ {
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+ all_values_count += list[i]->nvalues;
+ i++;
+ }
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (ListValue **)
+ palloc0(all_values_count * sizeof(ListValue *));
+ j = 0;
+ for (i = 0; i < nparts; i++)
+ {
+ int k;
+
+ for (k = 0; k < list[i]->nvalues; k++)
+ {
+ ListValue *list_value;
+
+ list_value = (ListValue *) palloc0(sizeof(ListValue));
+ list_value->value = datumCopy(list[i]->values[k],
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ list_value->index = i;
+ all_values[j++] = list_value;
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ range_parts = (RangePartition **)
+ palloc0(nparts * sizeof(RangePartition *));
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundRange *range_spec;
+ RangePartition *range_part;
+
+ Assert(IsA(lfirst(cell), PartitionBoundRange));
+ range_spec = (PartitionBoundRange *) lfirst(cell);
+ range_part = (RangePartition *)
+ palloc0(nparts * sizeof(RangePartition));
+ range_part->oid = oids[i];
+ range_part->range = make_range_from_spec(key, range_spec);
+ range_parts[i++] = range_part;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->bounds = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ ListInfo *listinfo;
+ int *mapping;
+ int next_index = 0;
+
+ listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+ mapping = (int *) palloc(sizeof(int) * nparts);
+
+ /* Initialize with invalid mapping values */
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ /* Sort so that we can perform binary search over values */
+ qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+ list_value_cmp, (void *) key);
+
+ listinfo->nvalues = all_values_count;
+ listinfo->has_null = found_null_partition;
+ listinfo->values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo->indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they are same for any two partitioning schemes
+ * with the same lists. One way to achieve this is to assign
+ * indexes in the same order as the least values of the
+ * individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo->values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ listinfo->indexes[i] = all_values[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo->indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo->null_index = mapping[null_partition_index];
+ else
+ listinfo->null_index = -1;
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of list
+ * values (viz. ordering of list values).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+
+ result->bounds->listinfo = listinfo;
+ pfree(mapping);
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ RangeInfo *rangeinfo;
+
+ rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+ rangeinfo->ranges = (PartitionRange **)
+ palloc0(nparts * sizeof(PartitionRange *));
+
+ /*
+ * Sort so that we can perform binary search over ranges.
+ * Note that this will also sort oids together.
+ */
+ qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+ range_partition_cmp, (void *) key);
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = range_parts[i]->oid;
+ rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+ key);
+ }
+
+ result->bounds->rangeinfo = rangeinfo;
+ break;
+ }
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2, int n)
+{
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+ return false;
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent. Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ Relation parent = heap_open(parentId, NoLock); /* already locked */
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionBoundList *list;
+
+ Assert(IsA(bound, PartitionBoundList));
+ list = (PartitionBoundList *) bound;
+
+ if (pdesc->nparts > 0)
+ {
+ ListInfo *listinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->listinfo);
+ listinfo = pdesc->bounds->listinfo;
+ if (list_overlaps_existing_partition(key, list, listinfo,
+ &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, list->location)));
+ }
+ }
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionBoundRange *range;
+
+ Assert(IsA(bound, PartitionBoundRange));
+ range = (PartitionBoundRange *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ RangeInfo *rangeinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+ rangeinfo = pdesc->bounds->rangeinfo;
+ if (range_overlaps_existing_partition(key, range, rangeinfo,
+ pdesc->nparts, &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, range->location)));
+ }
+ }
+ break;
+ }
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_partition_ancestors
+ *
+ * Returns list of ancestors all the way up to the root table
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+ Oid parentOID = InvalidOid;
+ Relation rel;
+
+ rel = heap_open(relid, AccessShareLock);
+ if (rel->rd_rel->relispartition)
+ parentOID = get_partition_parent(relid);
+ heap_close(rel, AccessShareLock);
+
+ if (!OidIsValid(parentOID))
+ return NIL;
+
+ return list_concat(list_make1_oid(parentOID),
+ get_partition_ancestors(parentOID));
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ int i;
+ ListCell *partexprs_item;
+
+ Assert(key);
+
+ if (IsA(bound, PartitionBoundList))
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+ Assert(key->strategy == PARTITION_STRAT_LIST);
+ my_qual = get_qual_for_list(key, list_spec);
+ }
+ else if (IsA(bound, PartitionBoundRange))
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+ Assert(key->strategy == PARTITION_STRAT_RANGE);
+ my_qual = get_qual_for_range(key, range_spec);
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ /* Find all attributes referenced and translate each reference */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+ return list_values_cmp((PartitionKey) arg,
+ (*(const ListValue **) a)->value,
+ (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+ return partition_range_cmp((PartitionKey) arg,
+ (*(const RangePartition **) a)->range,
+ (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with)
+{
+ int i;
+ PartitionList *new_list;
+
+ Assert(listinfo);
+ Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+ new_list = make_list_from_spec(key, list_spec);
+
+ if (new_list->has_null && listinfo->has_null)
+ {
+ *with = listinfo->null_index;
+ return true;
+ }
+
+ for (i = 0; i < new_list->nvalues; i++)
+ {
+ int found;
+
+ /* bsearch a new list's value in listinfo->values */
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ new_list->values[i],
+ key);
+ if (found >= 0)
+ {
+ *with = listinfo->indexes[found];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+ PartitionRangeBound *lower,
+ *upper;
+
+ range = make_range_from_spec(key, range_spec);
+ lower = range->lower;
+ upper = range->upper;
+
+ /*
+ * Range is not empty if one (and only one) of the bounds is infinity.
+ * Both cannot be infinity because of how the syntax is specified.
+ */
+ Assert(!lower->infinite || !upper->infinite);
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper < lower, then it's outright empty. Also if lower = upper
+ * and either is exclusive.
+ */
+ if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+ (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n, int *with)
+{
+ int i;
+ PartitionRange *range;
+
+ /* Create internal representation of range from range_spec */
+ range = make_range_from_spec(key, range_spec);
+
+ Assert(rangeinfo);
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+ {
+ *with = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+ partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+ return true;
+
+ if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+ partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * translate_var_attno
+ *
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand). If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, leave out the test but remove null Const from the list
+ * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The
+ * latter because null-valued expressions does not have the desired
+ * behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(list_spec->values); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ list_spec->values = list_delete_cell(list_spec->values,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = list_spec->values;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->parttypcoll[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ return nulltest1 ? list_make2(nulltest1, opexpr)
+ : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+ list_make2(nulltest2, opexpr),
+ -1))
+ : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ Oid operoid;
+ uint16 strategy;
+ bool need_relabel;
+
+ /*
+ * Handle the case where the partition is bounded on only one side.
+ *
+ * In this case, consider only the first column of the key since
+ * comparison with only the first column would have determined whether
+ * whether a row went into such partition. In other words, it always
+ * follows that -INF < someval and someval < +INF.
+ */
+ if (spec->lower == NIL || spec->upper == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ bool islower,
+ inclusive;
+ NullTest *nulltest;
+
+ if (spec->lower != NIL)
+ {
+ values = spec->lower;
+ islower = true;
+ inclusive = spec->lowerinc;
+ }
+ else
+ {
+ values = spec->upper;
+ islower = false;
+ inclusive = spec->upperinc;
+ }
+
+ /* Left operand */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /* Right operand */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ /* Get the correct btree operator for given strategy */
+ operoid = get_partition_operator(key, 0, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+
+ /* Build the opexpr and return the list containing it and nulltest */
+ return list_make2(nulltest,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->parttypcoll[0]));
+ }
+
+ /*
+ * We must consider both the lower and upper bounds. Iterate over
+ * columns of the key.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lower, cell2, spec->upper)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Is lower_val = upper_val?
+ */
+
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->parttypcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /*
+ * Yes, build leftop eq lower_val
+ */
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->parttypcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->parttypcoll[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->parttypcoll[i]));
+
+ /* No need to constrain further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ *
+ * Use either the column type as the operator datatype or opclass's declared
+ * input type.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ if (need_relabel)
+ *need_relabel = false;
+
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /* Turn that bound into a list of equivalent check quals */
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+ PartitionList *list;
+ ListCell *cell;
+ int i,
+ num_non_null;
+
+
+ list = (PartitionList *) palloc0(sizeof(PartitionList));
+ list->has_null = false;
+
+ /* Never put a null into the values array, flag instead */
+ num_non_null = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->has_null = true;
+ else
+ num_non_null++;
+ }
+
+ list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+ i = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (!val->constisnull)
+ list->values[i++] = datumCopy(val->constvalue,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ }
+
+ list->nvalues = num_non_null;
+
+ return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->parttypcoll[0],
+ val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+
+ range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ range->lower = make_range_bound(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ range->upper = make_range_bound(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+
+ return range;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->infinite = (val == NIL);
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->val[i] = datumCopy(val->constvalue,
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ i++;
+ }
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+ PartitionRange *result;
+
+ result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ result->lower = copy_range_bound(src->lower, key);
+ result->upper = copy_range_bound(src->upper, key);
+
+ return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+ int i;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->lower,
+ r2->ranges[i]->lower) != 0)
+ return false;
+
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->upper,
+ r2->ranges[i]->upper) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ int cmp;
+
+ cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+ if (cmp == 0)
+ cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+ return cmp;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Compare two composite keys
+ */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->parttypcoll[i],
+ val1[i], val2[i]));
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 874b320..106508e 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_table_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
#include "parser/parse_clause.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bdeafdc..e3cdf2f 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"
@@ -164,6 +165,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -280,7 +283,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -445,6 +449,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprbin, Oid *partopclass);
+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);
/* ----------------------------------------------------------------
@@ -483,6 +492,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -596,10 +606,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
@@ -674,6 +690,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -697,6 +731,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -994,6 +1029,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
* but RemoveRelations() can only pass one relkind for a given relation.
@@ -1082,7 +1124,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1460,7 +1503,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;
@@ -1505,8 +1549,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),
@@ -1533,6 +1577,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),
@@ -1568,18 +1624,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -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 (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2441,7 +2530,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2589,11 +2678,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2835,8 +2925,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3115,6 +3208,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 +3530,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);
@@ -3502,7 +3606,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3751,6 +3861,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 +4052,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4016,6 +4132,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4080,6 +4197,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4269,6 +4395,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4466,7 +4598,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4788,6 +4921,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5310,6 +5448,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5835,6 +5993,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5927,9 +6090,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6318,8 +6483,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7881,7 +8048,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);
@@ -7940,8 +8109,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8013,6 +8184,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10180,6 +10356,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10192,12 +10373,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;
@@ -10242,37 +10418,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10307,6 +10457,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);
@@ -10321,16 +10534,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;
}
/*
@@ -10381,7 +10586,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
@@ -10399,12 +10604,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10426,14 +10635,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10444,8 +10657,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10460,7 +10675,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10474,7 +10691,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.
@@ -10493,10 +10710,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10543,7 +10764,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10551,7 +10774,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10562,6 +10787,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10575,7 +10801,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10586,6 +10814,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10599,13 +10867,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];
@@ -10614,19 +10880,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10636,7 +10893,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);
@@ -10657,11 +10914,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
@@ -10670,7 +10936,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)))
@@ -10732,7 +10998,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);
@@ -10763,7 +11029,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)
@@ -10775,30 +11041,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;
}
/*
@@ -12414,3 +12670,271 @@ ComputePartitionAttrs(Relation rel, 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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a295162..61afe69 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4201,6 +4203,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+ PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+ PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5121,6 +5160,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundList:
+ retval = _copyPartitionBoundList(from);
+ break;
+ case T_PartitionBoundRange:
+ retval = _copyPartitionBoundRange(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index fa5037b..69aed5a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2656,6 +2658,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3415,6 +3448,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundList:
+ retval = _equalPartitionBoundList(a, b);
+ break;
+ case T_PartitionBoundRange:
+ retval = _equalPartitionBoundRange(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f0b63f1..55628f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3289,6 +3291,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3878,6 +3899,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundList:
+ _outPartitionBoundList(str, obj);
+ break;
+ case T_PartitionBoundRange:
+ _outPartitionBoundRange(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+ READ_LOCALS(PartitionBoundList);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+ READ_LOCALS(PartitionBoundRange);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2526,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionBoundList();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionBoundRange();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a2e99a6..0e22d64 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -546,6 +546,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partspec> PartitionSpec OptPartitionSpec
%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.
@@ -571,7 +579,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
@@ -587,7 +595,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
@@ -601,7 +610,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 +2382,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 +2509,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionBoundList *n = makeNode(PartitionBoundList);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2980,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3063,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3090,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3112,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3126,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 +3148,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 +4700,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;
+ }
;
/*****************************************************************************
@@ -11149,6 +11347,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;
@@ -13748,6 +13947,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13794,6 +13994,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13836,6 +14037,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..086de94 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
-
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c32edb7..c47e711 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +92,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partspec)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -360,6 +373,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);
@@ -899,6 +913,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;
@@ -1118,6 +1133,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;
@@ -2581,6 +2597,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2663,6 +2680,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;
@@ -3027,3 +3060,334 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_key_strategy(key);
+ int partnatts = get_partition_key_natts(key);
+ List *partexprs = get_partition_key_exprs(key);
+ PartitionBoundList *list, *result_list;
+ PartitionBoundRange *range, *result_range;
+
+ switch (strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (!IsA(bound, PartitionBoundList))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionBoundList *) bound;
+ result_list = makeNode(PartitionBoundList);
+
+ foreach(cell, list->values)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ char *colname;
+
+ if (!IsA(bound, PartitionBoundRange))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionBoundRange *) bound;
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionBoundRange);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower && list_length(range->lower) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (range->lower && list_length(range->lower) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ if (range->upper && list_length(range->upper) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (range->upper && list_length(range->upper) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ if (range->lower)
+ {
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ colname = deparse_expression((Node *) list_nth(partexprs, i),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ colname = deparse_expression((Node *) list_nth(partexprs, i),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 57c2933..50e5fed 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1136,6 +1139,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->bounds != NULL)
+ {
+ if (pdesc2->bounds == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+ pdesc1->nparts))
+ return false;
+ }
+ else if (pdesc2->bounds != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1263,13 +1319,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2266,6 +2327,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2414,11 +2479,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2429,6 +2495,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);
@@ -2459,6 +2526,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
@@ -2514,6 +2584,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 */
@@ -3740,6 +3817,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5263,6 +5354,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..fcda8f0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..ea7806e
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection bounds; /* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_partition_ancestors(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundList,
+ T_PartitionBoundRange,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 373b8ed..ad331dc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -734,6 +735,40 @@ typedef struct PartitionSpec
int location; /* token location, or -1 if unknown */
} PartitionSpec;
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1561,7 +1596,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
@@ -1787,7 +1824,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 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 99f68c7..0818d9c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,7 @@ 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 */
} ParseExprKind;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c775ca4..cc51bc9 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -120,6 +120,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -607,6 +610,12 @@ get_partition_col_typmod(PartitionKey key, int col)
return key->parttypmod[col];
}
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR: source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 260ec34..a6484af 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -433,3 +433,191 @@ Table "public.describe_list_key"
Partition key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+ERROR: "part_a" is a partition of "parted"
+HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 7a1f8de..008ffd4 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -414,3 +414,142 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
--
1.7.1
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-6.patch (20.6K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-6.patch)
download | inline diff:
From 5d38432aef6e0b103fa97a43b2ef1d6fb67782eb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 85 +++++++++++++++++++++----
src/test/regress/expected/create_table.out | 39 +++++++++++
src/test/regress/sql/create_table.sql | 12 ++++
7 files changed, 393 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 03be202..8b84df4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundList:
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) 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_PartitionBoundRange:
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) 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 854dddc..b6b8832 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6141,6 +6141,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15318,6 +15375,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 */
@@ -15346,8 +15414,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;
@@ -15376,7 +15447,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");
}
@@ -15441,15 +15513,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 (");
@@ -15619,6 +15698,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 db6dc5c..9834599 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index a6484af..90fb006 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,45 @@ 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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- 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 008ffd4..8e1ffd5 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,18 @@ 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partition cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-6.patch (16.2K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-6.patch)
download | inline diff:
From 2eea296413455fb19ec5ce21d66eb84ef0fabb32 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.
If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
src/backend/optimizer/plan/initsplan.c | 17 ++-
src/backend/optimizer/prep/prepunion.c | 282 +++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
3 files changed, 224 insertions(+), 84 deletions(-)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *rte = root->simple_rte_array[rti];
- if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+ if (brel == NULL)
+ continue;
+
+ /*
+ * If an "other rel" RTE is a "partitioned table", we must propagate
+ * the lateral info inherited all the way from the root parent to its
+ * children. That's because the children are not linked directly with
+ * the root parent via AppendRelInfo's unlike in case of a regular
+ * inheritance set (see expand_inherited_rtentry()). Failing to
+ * do this would result in those children not getting marked with the
+ * appropriate lateral info.
+ */
+ if (brel->reloptkind != RELOPT_BASEREL &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
continue;
if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /*
+ * Do not flatten the inheritance hierarchy if partitioned table, unless
+ * this is the result relation.
+ */
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+ rti != root->parse->resultRelation)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
- */
- if (childOID != parentOID)
- {
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
- }
-
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (oldrc)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-6.patch (30.2K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-6.patch)
download | inline diff:
From 606a2236f804b9b34a2f8dbe70dba7ca73fc3b7a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index be3fbc9..157d219 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,7 +2488,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 5790edc..5b0e8cf 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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-6.patch (8.0K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-6.patch)
download | inline diff:
From 670e4866f959549def0cbee0619b73c3818b2181 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 206 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 211 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6af629c..2d1602b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
PartitionRange *range;
} RangePartition;
+/*
+ * 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_parts 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 RelationBuildPartitionDesc() */
static int32 list_value_cmp(const void *a, const void *b, void *arg);
static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
/* Support RelationGetPartitionQual() */
static List *generate_partition_qual(Relation rel, bool recurse);
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -774,6 +833,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1423,6 +1529,106 @@ generate_partition_qual(Relation rel, bool recurse)
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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 ea7806e..1ecd5d6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -56,4 +57,8 @@ extern List *get_partition_ancestors(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-6.patch (41.0K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-6.patch)
download | inline diff:
From 6fcded2ed769ef7dc34a514d211a865309f1d7e8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 343 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 ++++-
src/backend/executor/nodeModifyTable.c | 123 +++++++++++
src/backend/optimizer/plan/createplan.c | 60 ++++++
src/backend/optimizer/util/plancat.c | 20 ++-
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, 909 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 2d1602b..5904bb6 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
PartitionRangeBound *b2);
static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
/*
* RelationBuildPartitionDesc
@@ -1545,7 +1560,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;
@@ -1629,6 +1644,267 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ ListInfo *listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->bounds->listinfo != NULL);
+ listinfo = pdesc->bounds->listinfo;
+
+ if (isnull && listinfo->has_null)
+ return listinfo->null_index;
+ else if (!isnull)
+ {
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ value,
+ key);
+ if (found >= 0)
+ return listinfo->indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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);
+ Assert(pdesc->bounds->rangeinfo != NULL);
+
+ return bsearch_ranges(key, pdesc->nparts,
+ pdesc->bounds->rangeinfo, tuple);
+}
+
/* List partition related support functions */
/*
@@ -1976,3 +2252,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
return result;
}
+
+/*
+ * bsearch_ranges
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = n - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (rangeinfo->ranges[idx]->upper->infinite)
+ {
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+ return idx;
+
+ break;
+ }
+ else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+ {
+ if (rangeinfo->ranges[idx]->lower->infinite)
+ return idx;
+
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->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, PartitionRangeBound *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, PartitionRangeBound *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 157d219..932ed62 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.
@@ -1382,6 +1388,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1677,6 +1771,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1690,6 +1786,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);
}
@@ -2240,6 +2353,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2260,7 +2374,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2368,6 +2483,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2395,6 +2511,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;
@@ -2416,10 +2533,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 */
@@ -2471,6 +2624,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 */
@@ -2491,7 +2669,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)
@@ -2521,7 +2708,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),
@@ -2541,6 +2729,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2554,7 +2748,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 e3cdf2f..0a3a367 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1245,6 +1245,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 5b0e8cf..cb47035 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,79 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +2063,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 47158f6..32f4031 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_table_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6152,6 +6153,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 (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ 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 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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 6901e08..ced820f 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_table_fn.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -798,8 +799,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 1ecd5d6..b6607d8 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/rel.h"
@@ -61,4 +63,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 697c90f..6e51773 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"
@@ -1141,6 +1142,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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-6.patch (24.7K, ../../7aa6a14d-9476-afd7-2c09-6a016eb8c12b@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-6.patch)
download | inline diff:
From 82577d46c647b48175a933a035deb004ae84fba3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 f43352c..8084029 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-09-30 00:10 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 2 replies; 329+ messages in thread
From: Robert Haas @ 2016-09-30 00:10 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> I removed DEPENDENCY_IGNORE. Does the following look good or am I still
> missing something?
You missed your commit message, but otherwise looks fine.
>> Also, I think this should be rephrased a bit to be more clear about
>> how the partitioning key works, like this: The optional
>> <literal>PARTITION BY</literal> clause specifies a method of
>> partitioning the table. The table thus created is called a
>> <firstterm>partitioned</firstterm> table. The parenthesized list of
>> expressions forms the <firsttem>partitioning key</firstterm> for the
>> table. When using range partitioning, the partioning key can include
>> multiple columns or expressions, but for list partitioning, the
>> partitioning key must consist of a single column or expression. If no
>> btree operator class is specified when creating a partitioned table,
>> the default btree operator class for the datatype will be used. If
>> there is none, an error will be reported.
>
> Revised text along these lines.
It seems you copied my typo: my text has "partioning" for
"partitioning" and your patch now has that, too.
> Things were that way initially, that is, the parent relations had no
> relfilenode. I abandoned that project however. The storage-less parent
> thing seemed pretty daunting to me to handle right away. For example,
> optimizer and the executor code needed to be taught about the parent rel
> appendrel member that used to be included as part of the result of
> scanning the inheritance set but was no longer.
Even if we leave the empty relfilenode around for now -- in the long
run I think it should die -- I think we should prohibit the creation
of subsidiary object on the parent which is only sensible if it has
rows - e.g. indexes. It makes no sense to disallow non-inheritable
constraints while allowing indexes, and it could box us into a corner
later.
>> + /*
>> + * 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.
>> + */
>>
>> I'm a bit confused by this, because I would think this processing
>> ought to have been done before storing anything in the system
>> catalogs. I don't see why it should be necessary to do it again after
>> pulling data back out of the system catalogs.
>
> The pattern matches what's done for other expressions that optimizer deals
> with, such as CHECK, index key, and index predicate expressions.
That's kind of a non-answer answer, but OK. Still, I think you
shouldn't just copy somebody else's comment blindly into a new place.
Reference the original comment, or write your own.
>> How can it be valid to have no partitioning expressions?
>
> Keys that are simply column names are resolved to attnums and stored
> likewise. If some key is an expression, then corresponding attnum is 0
> and the expression itself is added to the list that gets stored into
> partexprbin. It is doing the same thing as index expressions.
Oh, right. Oops.
>> + if (classform->relkind != relkind &&
>> + (relkind == RELKIND_RELATION &&
>> + classform->relkind != RELKIND_PARTITIONED_TABLE))
>>
>> That's broken. Note that all of the conditions are joined using &&,
>> so if any one of them fails then we won't throw an error. In
>> particular, it's no longer possible to throw an error when relkind is
>> not RELKIND_RELATION.
>
> You are right. I guess it would have to be the following:
>
> + if ((classform->relkind != relkind &&
> + classform->relkind != RELKIND_PARTITIONED_TABLE) ||
> + (classform->relkind == RELKIND_PARTITIONED_TABLE &&
> + relkind != RELKIND_RELATION))
>
> Such hackishness could not be helped because we have a separate DROP
> command for every distinct relkind, except we overload DROP TABLE for both
> regular and partitioned tables.
Maybe this would be better:
if (relkind == RELKIND_PARTITIONED_TABLE)
syntax_relkind = RELKIND_RELATION;
else
syntax_relkind = rekind;
if (classform->relkind != syntax_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
>> Why isn't this logic being invoked from transformCreateStmt()? Then
>> we could use the actual parseState for the query instead of a fake
>> one.
>
> Because we need an open relation for it to work, which in this case there
> won't be until after we have performed heap_create_with_catalog() in
> DefineRelation(). Mainly because we need to perform transformExpr() on
> expressions. That's similar to how cookConstraint() on the new CHECK
> constraints cannot be performed earlier. Am I missing something?
Hmm, yeah, I guess that's the same thing. I guess I got on this line
of thinking because the function name started with "transform" which
is usually something happening during parse analysis only. Maybe add
a code comment explaining why the work can't be done sooner, just like
what you've written here.
> Hmm, I guess it wouldn't hurt to just leave any COLLATE clauses as it is -
> just remove the above code. Of course, we must then record the collation
> in the catalog alongside other user-specified information such as operator
> class. Currently, if the key is a simple column we use its attcollation
> and if it's an expression then we use its exprCollation().
>
> When I first wrote it, I wasn't sure what the implications of explicit
> collations would be for partitioning. There are two places where it comes
> into play: a) when comparing partition key values using the btree compare
> function b) embedded as varcollid and inputcollid in implicitly generated
> check constraints for partitions. In case of the latter, any mismatch
> with query-specified collation causes constraint exclusion proof to be
> canceled. When it's default collations everywhere, the chances of that
> sort of thing happening are less. If we support user-specified collations
> on keys, then things will get a little bit more involved.
I think it's worth rewinding to general principles here: the only time
a collation has real meaning is in the context of a comparison
operation. If we ask whether A < B, the answer may depend on the
collation that is used to perform the comparison. Any other place
that mentions a collation is only trying to influence the collation
that gets used for some comparison that will happen later - so, for
example, for a table column, attcollation is setting the default
collation for comparisons involving that column. The column is not
itself collated; that doesn't really make sense.
In the case of partitioning, there is really exactly one thing that
matters: everybody's got to agree on the collation to be used to
compare actual or hypothetical values of the partitioning columns
against the partition bounds. If we've got a set of partition bounds
b_1, b_2, ..., b_n and a set of partitions p_1, p_2, ..., p_{n-1} such
that a row in p_i must have a key k such that b_i < k and k < b_{i+1},
and if the meaning of that "<" operator is collation-dependent, then
we had better agree on which collation is in use every single time we
do the test.
Indexes, of course, have this exact same problem, at least if, like
btree, they rely on ordering. We search the index by applying the "<"
operator to compare various values taken from the index tuples with
the value we're trying to find, and it is absolutely vital that the
collation used for lookups remain constant and that it is the same as
the collation used for inserts, which had also better remain constant.
That is why pg_index has an indcollation column of type oidvector: it
tells us which collation to use for each index column. It pairs with
indclass, which tells us which operator class to use for each index
column. I think that partitioning will need exact analogues -
partclass and partcollation - because it has exactly the same problem.
Currently, you've got partclass but not partcollation.
I'd in general recommend that you try to follow the index precedent
here as closely as practical.
> However in the present case, this is just one side of a whole partition
> constraint (the other piece being individual partition's bound value), so
> should be treated a bit differently from the CHECK constraints. I modeled
> this on ComputeIndexAttrs() checks viz. the following:
>
> /*
> * An expression using mutable functions is probably wrong,
> * since if you aren't going to get the same result for the
> * same data every time, it's not clear what the index entries
> * mean at all.
> */
> if (CheckMutability((Expr *) expr))
> ereport(ERROR,
> (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
> errmsg("functions in index expression must be marked IMMUTABLE")));
>
> Likewise if a partition key expression contained mutable functions, same
> input row could be mapped to different partitions based on the result of
> expression computed using the input values. So, it seems prudent to
> enforce immutability unlike CHECK constraints. Am I missing something?
OK, seems reasonable.
>> + char relkind = ((CreateStmt *)
>> stmt)->partby != NULL
>> + ? RELKIND_PARTITIONED_TABLE
>> + : RELKIND_RELATION;
>>
>> Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
>> NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
>> RELKIND_PARTITION_TABLE; }
>
> Done. By the way, I made the ereport say the following: "unexpected
> relkind value passed to DefineRelation", did you intend it to say
> something else?
You don't need it to be translatable because it should only happen if
there's a bug in the code; users shouldn't actually be able to hit it.
So use elog() rather than ereport(). You don't need to include the
function name, either; the user can use \errverbose or \set VERBOSITY
verbose to get the file and line number if they need it. So just
elog(ERROR, "unexpected relkind") should be fine; or maybe elog(ERROR,
"unexpected relkind: %d", (int) relkind), following similar precedent
elsewhere.
Reading through the latest 0001:
+ The catalog <structname>pg_partitioned_table</structname> stores information
+ about the partition key of tables.
Maybe "stores information about how tables are partitioned".
+ Partitioning strategy (or method); <literal>l</> = list
partitioned table,
I don't think we need "(or method)".
+ <entry><structfield>partexprbin</structfield></entry>
Is there any good reason not to do partexprbin -> partexpr throughout the patch?
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
I would delete "in turn" and the subsequent comma, so that it says
"which are created using separate".
+ 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 or expressions in the partition key and partition
+ bound constraints associated with the individual partitions.
How about: "A data row inserted into the table is routed to a
partition based on the value of columns or expressions in the
partition key. If no existing partition matches the values in the new
row, an error will occur."
+ Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
+ KEY constraints; however, you can define these constraints on individual
+ data partitions.
Delete "data". Add <literal> tags around the keywords.
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
This seems pointless. It's hard to see how the tuple could already
exist, but if it does, this will fail a little later on anyway when we
do simple_heap_insert() and CatalogUpdateIndexes() for the new tuple.
In general, if you're doing work only to support an Assert(), you
should put #ifdef USE_ASSERT_CHECKING around it, but in this case I'd
just rip this out.
+ myself.objectId = RelationGetRelid(rel);;
Extra semicolon.
+ /*
+ * 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.
+ */
I think this comment is spending a lot of time explaining the
mechanism when what it should be doing is explaining why this case
arises here and not elsewhere.
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
Extra space before &&.
+ if(partattno != 0)
Missing space.
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators which seems enough for list and range partitioning.
Probably best to add a comma before "which".
break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
break;
default:
Missing break.
+ n->strategy = PARTITION_STRAT_RANGE;
Let's not abbreviate STRATEGY to STRAT in the names of these constants.
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
I think you should say "PARTITION BY" here. See the function header
comment for ParseExprKindName.
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list
partitioning.")));
How about combining these two: cannot list partition using more than one column
+ * 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.
Again, don't just copy existing comments. Refer to them.
+ * To retrieve further variable-length attributes, we'd need the catlog's
Typo.
+ * pg_partitioned_table_fn.h
+ * prototypes for functions in catalog/pg_partitioned_table.c
Is it really worth having a separate header file for ONE function?
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
I bet you can avoid making this a keyword. PARTITION BY IDENT in the
grammar, or something like that.
+CREATE TABLE pkrel(
+ a int PRIMARY KEY
+);
Add a space.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-03 04:26 Michael Paquier <michael.paquier@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 329+ messages in thread
From: Michael Paquier @ 2016-10-03 04:26 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Sep 30, 2016 at 9:10 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> I removed DEPENDENCY_IGNORE. Does the following look good or am I still
>> missing something?
>
> You missed your commit message, but otherwise looks fine.
I have moved this patch to next CF because that's fresh, but switched
the patch as "waiting on author". Be careful, the patch was in "needs
review".
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-03 04:45 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Michael Paquier <michael.paquier@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-03 04:45 UTC (permalink / raw)
To: Michael Paquier <michael.paquier@gmail.com>; Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/03 13:26, Michael Paquier wrote:
> On Fri, Sep 30, 2016 at 9:10 AM, Robert Haas <robertmhaas@gmail.com> wrote:
>> On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> I removed DEPENDENCY_IGNORE. Does the following look good or am I still
>>> missing something?
>>
>> You missed your commit message, but otherwise looks fine.
>
> I have moved this patch to next CF because that's fresh, but switched
> the patch as "waiting on author". Be careful, the patch was in "needs
> review".
Thanks Michael. I was going to post the patch addressing Robert's
comments today, which I will anyway. Then I will switch the status back
to "Needs Review", albeit as part of the next CF.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-04 08:02 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 3 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-04 08:02 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Thanks for the comments.
On 2016/09/30 9:10, Robert Haas wrote:
> On Thu, Sep 29, 2016 at 8:09 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Things were that way initially, that is, the parent relations had no
>> relfilenode. I abandoned that project however. The storage-less parent
>> thing seemed pretty daunting to me to handle right away. For example,
>> optimizer and the executor code needed to be taught about the parent rel
>> appendrel member that used to be included as part of the result of
>> scanning the inheritance set but was no longer.
>
> Even if we leave the empty relfilenode around for now -- in the long
> run I think it should die -- I think we should prohibit the creation
> of subsidiary object on the parent which is only sensible if it has
> rows - e.g. indexes. It makes no sense to disallow non-inheritable
> constraints while allowing indexes, and it could box us into a corner
> later.
I agree. So we must prevent from the get-go the creation of following
objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):
* Indexes
* Row triggers (?)
In addition to preventing creation of these objects, we must also teach
commands that directly invoke heapam.c to skip such relations.
I have not implemented that in the patch yet though.
>>> + /*
>>> + * 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.
>>> + */
>>>
>>> I'm a bit confused by this, because I would think this processing
>>> ought to have been done before storing anything in the system
>>> catalogs. I don't see why it should be necessary to do it again after
>>> pulling data back out of the system catalogs.
>>
>> The pattern matches what's done for other expressions that optimizer deals
>> with, such as CHECK, index key, and index predicate expressions.
>
> That's kind of a non-answer answer, but OK. Still, I think you
> shouldn't just copy somebody else's comment blindly into a new place.
> Reference the original comment, or write your own.
Sorry I could have explained a bit more in my previous message. I rewrote
the comment as follows:
/*
* Run the expressions through const-simplification since the planner
* will be comparing them to similarly-processed qual clause operands,
* and may fail to detect valid matches without this step. We don't
* need to bother with canonicalize_qual() though, because partition
* expressions are not full-fledged qualification clauses.
*/
>>> + if (classform->relkind != relkind &&
>>> + (relkind == RELKIND_RELATION &&
>>> + classform->relkind != RELKIND_PARTITIONED_TABLE))
>>>
>>> That's broken. Note that all of the conditions are joined using &&,
>>> so if any one of them fails then we won't throw an error. In
>>> particular, it's no longer possible to throw an error when relkind is
>>> not RELKIND_RELATION.
>>
>> You are right. I guess it would have to be the following:
>>
>> + if ((classform->relkind != relkind &&
>> + classform->relkind != RELKIND_PARTITIONED_TABLE) ||
>> + (classform->relkind == RELKIND_PARTITIONED_TABLE &&
>> + relkind != RELKIND_RELATION))
>>
>> Such hackishness could not be helped because we have a separate DROP
>> command for every distinct relkind, except we overload DROP TABLE for both
>> regular and partitioned tables.
>
> Maybe this would be better:
>
> if (relkind == RELKIND_PARTITIONED_TABLE)
> syntax_relkind = RELKIND_RELATION;
> else
> syntax_relkind = rekind;
> if (classform->relkind != syntax_relkind)
> DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
In this case, relkind refers to the command viz. DROP <ObjectType/relkind>
<relname>. It can never be RELKIND_PARTITIONED_TABLE, so the above will
be equivalent to leaving things unchanged. Anyway I agree the suggested
style is better, but I assume you meant:
if (classform->relkind == RELKIND_PARTITIONED_TABLE)
expected_relkind = RELKIND_RELATION;
else
expected_relkind = classform->relkind;
if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
>>> Why isn't this logic being invoked from transformCreateStmt()? Then
>>> we could use the actual parseState for the query instead of a fake
>>> one.
>>
>> Because we need an open relation for it to work, which in this case there
>> won't be until after we have performed heap_create_with_catalog() in
>> DefineRelation(). Mainly because we need to perform transformExpr() on
>> expressions. That's similar to how cookConstraint() on the new CHECK
>> constraints cannot be performed earlier. Am I missing something?
>
> Hmm, yeah, I guess that's the same thing. I guess I got on this line
> of thinking because the function name started with "transform" which
> is usually something happening during parse analysis only. Maybe add
> a code comment explaining why the work can't be done sooner, just like
> what you've written here.
Added the comment.
>> Hmm, I guess it wouldn't hurt to just leave any COLLATE clauses as it is -
>> just remove the above code. Of course, we must then record the collation
>> in the catalog alongside other user-specified information such as operator
>> class. Currently, if the key is a simple column we use its attcollation
>> and if it's an expression then we use its exprCollation().
>>
>> When I first wrote it, I wasn't sure what the implications of explicit
>> collations would be for partitioning. There are two places where it comes
>> into play: a) when comparing partition key values using the btree compare
>> function b) embedded as varcollid and inputcollid in implicitly generated
>> check constraints for partitions. In case of the latter, any mismatch
>> with query-specified collation causes constraint exclusion proof to be
>> canceled. When it's default collations everywhere, the chances of that
>> sort of thing happening are less. If we support user-specified collations
>> on keys, then things will get a little bit more involved.
>
> I think it's worth rewinding to general principles here: the only time
> a collation has real meaning is in the context of a comparison
> operation. If we ask whether A < B, the answer may depend on the
> collation that is used to perform the comparison. Any other place
> that mentions a collation is only trying to influence the collation
> that gets used for some comparison that will happen later - so, for
> example, for a table column, attcollation is setting the default
> collation for comparisons involving that column. The column is not
> itself collated; that doesn't really make sense.
>
> In the case of partitioning, there is really exactly one thing that
> matters: everybody's got to agree on the collation to be used to
> compare actual or hypothetical values of the partitioning columns
> against the partition bounds. If we've got a set of partition bounds
> b_1, b_2, ..., b_n and a set of partitions p_1, p_2, ..., p_{n-1} such
> that a row in p_i must have a key k such that b_i < k and k < b_{i+1},
> and if the meaning of that "<" operator is collation-dependent, then
> we had better agree on which collation is in use every single time we
> do the test.
>
> Indexes, of course, have this exact same problem, at least if, like
> btree, they rely on ordering. We search the index by applying the "<"
> operator to compare various values taken from the index tuples with
> the value we're trying to find, and it is absolutely vital that the
> collation used for lookups remain constant and that it is the same as
> the collation used for inserts, which had also better remain constant.
> That is why pg_index has an indcollation column of type oidvector: it
> tells us which collation to use for each index column. It pairs with
> indclass, which tells us which operator class to use for each index
> column. I think that partitioning will need exact analogues -
> partclass and partcollation - because it has exactly the same problem.
> Currently, you've got partclass but not partcollation.
>
> I'd in general recommend that you try to follow the index precedent
> here as closely as practical.
Thanks for the explanation. I added a new field to the catalog called
partcollation.
That means a couple of things - when comparing input key values (consider
a new tuple being routed) to partition bounds using the btree compare
function, we use the corresponding key column's partcollation. The same
is also used as inputcollid of the OpExpr (or ScalarArrayOpExpr) in the
implicitly generated CHECK constraints.
However, varcollid of any Var nodes in the above expressions is still the
corresponding attributes' attcollation.
Needless to say, a query-specified qualification clause must now include
the same collate clause as used for individual partition columns (or
expressions) for the constraint exclusion to work as intended.
>>> + char relkind = ((CreateStmt *)
>>> stmt)->partby != NULL
>>> + ? RELKIND_PARTITIONED_TABLE
>>> + : RELKIND_RELATION;
>>>
>>> Let's push this down into DefineRelation(). i.e. if (stmt->partby !=
>>> NULL) { if (relkind != RELKIND_RELATION) ereport(...); relkind =
>>> RELKIND_PARTITION_TABLE; }
>>
>> Done. By the way, I made the ereport say the following: "unexpected
>> relkind value passed to DefineRelation", did you intend it to say
>> something else?
>
> You don't need it to be translatable because it should only happen if
> there's a bug in the code; users shouldn't actually be able to hit it.
> So use elog() rather than ereport(). You don't need to include the
> function name, either; the user can use \errverbose or \set VERBOSITY
> verbose to get the file and line number if they need it. So just
> elog(ERROR, "unexpected relkind") should be fine; or maybe elog(ERROR,
> "unexpected relkind: %d", (int) relkind), following similar precedent
> elsewhere.
Done as: elog(ERROR, "unexpected relkind: %d", (int) relkind)
> Reading through the latest 0001:
>
> + The catalog <structname>pg_partitioned_table</structname> stores information
> + about the partition key of tables.
>
> Maybe "stores information about how tables are partitioned".
Fixed.
> + Partitioning strategy (or method); <literal>l</> = list
> partitioned table,
>
> I don't think we need "(or method)".
Fixed.
>
> + <entry><structfield>partexprbin</structfield></entry>
>
> Is there any good reason not to do partexprbin -> partexpr throughout the patch?
Agreed. Though I used partexprs (like indexprs).
> + A partitioned table is divided into sub-tables (called partitions), which
> + in turn, are created using separate <literal>CREATE TABLE</> commands.
>
> I would delete "in turn" and the subsequent comma, so that it says
> "which are created using separate".
Done.
>
> + 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 or expressions in the partition key and partition
> + bound constraints associated with the individual partitions.
>
> How about: "A data row inserted into the table is routed to a
> partition based on the value of columns or expressions in the
> partition key. If no existing partition matches the values in the new
> row, an error will occur."
Done.
>
> + Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN
> + KEY constraints; however, you can define these constraints on individual
> + data partitions.
>
> Delete "data". Add <literal> tags around the keywords.
Done.
> + tuple = SearchSysCache1(PARTEDRELID,
> + ObjectIdGetDatum(RelationGetRelid(rel)));
> + /* Cannot already exist */
> + Assert(!HeapTupleIsValid(tuple));
>
> This seems pointless. It's hard to see how the tuple could already
> exist, but if it does, this will fail a little later on anyway when we
> do simple_heap_insert() and CatalogUpdateIndexes() for the new tuple.
> In general, if you're doing work only to support an Assert(), you
> should put #ifdef USE_ASSERT_CHECKING around it, but in this case I'd
> just rip this out.
OK, removed the Assert.
> + myself.objectId = RelationGetRelid(rel);;
>
> Extra semicolon.
Fixed.
> + /*
> + * 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.
> + */
>
> I think this comment is spending a lot of time explaining the
> mechanism when what it should be doing is explaining why this case
> arises here and not elsewhere.
Rewrote the comment as follows:
/*
* Anything mentioned in the expressions. We must ignore the column
* references which will count as self-dependency items; in this case,
* the depender is the table itself (there is no such thing as partition
* key object).
*/
> + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
>
> Extra space before &&.
Fixed.
>
> + if(partattno != 0)
>
> Missing space.
Fixed.
>
> + * Identify a btree opclass to use. Currently, we use only btree
> + * operators which seems enough for list and range partitioning.
>
> Probably best to add a comma before "which".
Done.
>
> break;
> case T_ForeignKeyCacheInfo:
> _outForeignKeyCacheInfo(str, obj);
> + case T_PartitionSpec:
> + _outPartitionSpec(str, obj);
> + break;
> + case T_PartitionElem:
> + _outPartitionElem(str, obj);
> break;
>
> default:
>
> Missing break.
Oops, fixed.
> + n->strategy = PARTITION_STRAT_RANGE;
>
> Let's not abbreviate STRATEGY to STRAT in the names of these constants.
Done.
>
> case EXPR_KIND_TRIGGER_WHEN:
> return "WHEN";
> + case EXPR_KIND_PARTITION_KEY:
> + return "partition key expression";
>
> I think you should say "PARTITION BY" here. See the function header
> comment for ParseExprKindName.
Used "PARTITION BY". I was trying to mimic EXPR_KIND_INDEX_EXPRESSION,
but this seems to work better. Also, I renamed EXPR_KIND_PARTITION_KEY to
EXPR_KIND_PARTITION_EXPRESSION.
By the way, a bunch of error messages say "partition key expression". I'm
assuming it's better to leave them alone, that is, not rewrite them as
"PARTITION BY expressions".
> + errmsg("cannot use more than one column in partition key"),
> + errdetail("Only one column allowed with list
> partitioning.")));
>
> How about combining these two: cannot list partition using more than one column
Done.
> + * 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.
>
> Again, don't just copy existing comments. Refer to them.
Rewrote the comment to explain two points: why build in the working
context instead of directly in the CacheMemoryContext and why use a
private child context of CacheMemoryContext to store the information.
> + * To retrieve further variable-length attributes, we'd need the catlog's
>
> Typo.
Fixed.
> + * pg_partitioned_table_fn.h
> + * prototypes for functions in catalog/pg_partitioned_table.c
>
> Is it really worth having a separate header file for ONE function?
Previously the two functions in this file were defined in catalog/heap.c
and I think they could be moved back there alongside such folks as
AddRelationNewConstraints, StoreAttrDefault, RemoveAttributeById,
RemoveStatistics, etc. So did.
There are no longer pg_partitioned_table.c and pg_partitioned_table_fn.h
files.
> +PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
>
> I bet you can avoid making this a keyword. PARTITION BY IDENT in the
> grammar, or something like that.
Managed to do that with one more production as follows:
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
And then PARTITION BY part_strategy
That's because "range" is already a keyword.
>
> +CREATE TABLE pkrel(
> + a int PRIMARY KEY
> +);
>
> Add a space.
Fixed.
Attached updated patches.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-7.patch (112.5K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-7.patch)
download | inline diff:
From 0e1f52f42c4473bfec4979a14df072ad6751a0cb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 160 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
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 | 434 +++++++++++++++++++++++++++-
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 | 34 +++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 257 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 5 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 36 +++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 66 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 137 +++++++++
51 files changed, 1905 insertions(+), 50 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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>partcollation</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 collation to use for partitioning.
+ </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>partattrs</>. 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..893c899 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,41 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partitioning key</firstterm>
+ for the table. When using range partitioning, the partitioning key can
+ include multiple columns or expressions, but for list partitioning, the
+ partitioning key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is routed
+ to a partition based on the value of columns or expressions in the
+ partition key. If no existing partition matches the values in the new
+ row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..2994cd0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1103,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1137,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1182,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1358,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1806,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2044,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -2983,3 +3006,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * 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,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition key, opclass info into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references which will count as self-dependency items; in this case,
+ * the depender is the table itself (there is no such thing as partition
+ * key object).
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 457c9bb..9801f0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..4e067d2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -371,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
@@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..230a7ad 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..237d0a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process and store partition key information, if any */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * 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 *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (is_expr)
+ *is_expr = false;
+ if (attnum == partattno)
+ return true;
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ if (is_expr)
+ *is_expr = true;
+
+ /* Find all attributes referenced */
+ 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;
+
+ if (attno == attnum)
+ return true;
+ }
+ partexprs_item = lnext(partexprs_item);
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5840,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 +5885,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 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8017,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7892,6 +8048,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7907,7 +8076,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8934,6 +9104,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9567,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9990,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10190,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10068,6 +10246,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11446,6 +11631,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12081,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12235,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12247,236 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partition strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears 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_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..51b6d17 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 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_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +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_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e80ff80 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;
/*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..95959c0
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..ada75bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ 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 *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name ('list', 'range', etc.)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ List *partexprs; /* partition key expressions, if any */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation */
+ Oid *partcollation;
+
+ /* Type information of partition attributes */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..5f31540 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ 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 list partition using more than one column
+-- 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: set-returning functions are not allowed in partition key expression
+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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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
+-- 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 a btree operator class or define a default btree operator class for the data 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 a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..48a660f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ 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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+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();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-7.patch (22.5K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-7.patch)
download | inline diff:
From 7e1985802e1946ba74d5fd0f7e086209336ba1fb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 140 ++++++++++++++++++++++++++++
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, 260 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..51e175e 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_table.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,144 @@ 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 relid = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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);
+
+ 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 299e887..e310b63 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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,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");
@@ -5296,7 +5301,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 "
@@ -5308,7 +5314,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,
@@ -5322,7 +5328,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,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)
{
@@ -5954,6 +5962,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 */
@@ -5998,7 +6007,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
@@ -15456,6 +15470,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15516,6 +15533,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15534,7 +15552,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15751,6 +15770,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 5f31540..36f487a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -411,3 +411,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: 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 48a660f..5a0d933 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-7.patch (205.0K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-7.patch)
download | inline diff:
From bb0097c43241f53e86e840bc9e316d87f7d17bf1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 33 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1772 ++++++++++++++++++++++++++++
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 774 ++++++++++--
src/backend/nodes/copyfuncs.c | 48 +
src/backend/nodes/equalfuncs.c | 42 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 33 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_agg.c | 1 -
src/backend/parser/parse_utilcmd.c | 366 ++++++-
src/backend/utils/cache/relcache.c | 105 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 59 +
src/include/catalog/pg_class.h | 22 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 42 +-
src/include/parser/kwlist.h | 3 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 219 ++++
src/test/regress/expected/create_table.out | 188 +++
src/test/regress/sql/alter_table.sql | 192 +++
src/test/regress/sql/create_table.sql | 139 +++
32 files changed, 4409 insertions(+), 162 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..a4fff4f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1043,10 +1122,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1054,7 +1135,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1233,6 +1315,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 893c899..dc07d32 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <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>
@@ -1424,7 +1494,38 @@ CREATE TABLE measurement (
CREATE TABLE cities (
name text not null,
population int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (lower(name));
+</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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2994cd0..5a6c742 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -91,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -771,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -809,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -819,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -925,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1034,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1269,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2479,7 +2498,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..05e63c3
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1772 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; maps
+ * element-by-element with the values array */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+ int nvalues;
+ Datum *values;
+ bool has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+ Datum value;
+ int index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+ Oid oid;
+ PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n,
+ int *with);
+
+/* Support get_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+ PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+ bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ ListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ RangePartition **range_parts;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ oids[i++] = partrelid;
+ }
+
+ /* Convert from node to a internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionList **list;
+ int j;
+
+ list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+ i = 0;
+ all_values_count = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundList *list_spec;
+
+ Assert(IsA(lfirst(cell), PartitionBoundList));
+ list_spec = (PartitionBoundList *) lfirst(cell);
+
+ list[i] = make_list_from_spec(key, list_spec);
+
+ if (list[i]->has_null)
+ {
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+ all_values_count += list[i]->nvalues;
+ i++;
+ }
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (ListValue **)
+ palloc0(all_values_count * sizeof(ListValue *));
+ j = 0;
+ for (i = 0; i < nparts; i++)
+ {
+ int k;
+
+ for (k = 0; k < list[i]->nvalues; k++)
+ {
+ ListValue *list_value;
+
+ list_value = (ListValue *) palloc0(sizeof(ListValue));
+ list_value->value = datumCopy(list[i]->values[k],
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ list_value->index = i;
+ all_values[j++] = list_value;
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ range_parts = (RangePartition **)
+ palloc0(nparts * sizeof(RangePartition *));
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundRange *range_spec;
+ RangePartition *range_part;
+
+ Assert(IsA(lfirst(cell), PartitionBoundRange));
+ range_spec = (PartitionBoundRange *) lfirst(cell);
+ range_part = (RangePartition *)
+ palloc0(nparts * sizeof(RangePartition));
+ range_part->oid = oids[i];
+ range_part->range = make_range_from_spec(key, range_spec);
+ range_parts[i++] = range_part;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->bounds = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListInfo *listinfo;
+ int *mapping;
+ int next_index = 0;
+
+ listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+ mapping = (int *) palloc(sizeof(int) * nparts);
+
+ /* Initialize with invalid mapping values */
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ /* Sort so that we can perform binary search over values */
+ qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+ list_value_cmp, (void *) key);
+
+ listinfo->nvalues = all_values_count;
+ listinfo->has_null = found_null_partition;
+ listinfo->values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo->indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they are same for any two partitioning schemes
+ * with the same lists. One way to achieve this is to assign
+ * indexes in the same order as the least values of the
+ * individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo->values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ listinfo->indexes[i] = all_values[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo->indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo->null_index = mapping[null_partition_index];
+ else
+ listinfo->null_index = -1;
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of list
+ * values (viz. ordering of list values).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+
+ result->bounds->listinfo = listinfo;
+ pfree(mapping);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ RangeInfo *rangeinfo;
+
+ rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+ rangeinfo->ranges = (PartitionRange **)
+ palloc0(nparts * sizeof(PartitionRange *));
+
+ /*
+ * Sort so that we can perform binary search over ranges.
+ * Note that this will also sort oids together.
+ */
+ qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+ range_partition_cmp, (void *) key);
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = range_parts[i]->oid;
+ rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+ key);
+ }
+
+ result->bounds->rangeinfo = rangeinfo;
+ break;
+ }
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2, int n)
+{
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent. Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ Relation parent = heap_open(parentId, NoLock); /* already locked */
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionBoundList *list;
+
+ Assert(IsA(bound, PartitionBoundList));
+ list = (PartitionBoundList *) bound;
+
+ if (pdesc->nparts > 0)
+ {
+ ListInfo *listinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->listinfo);
+ listinfo = pdesc->bounds->listinfo;
+ if (list_overlaps_existing_partition(key, list, listinfo,
+ &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, list->location)));
+ }
+ }
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionBoundRange *range;
+
+ Assert(IsA(bound, PartitionBoundRange));
+ range = (PartitionBoundRange *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ RangeInfo *rangeinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+ rangeinfo = pdesc->bounds->rangeinfo;
+ if (range_overlaps_existing_partition(key, range, rangeinfo,
+ pdesc->nparts, &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, range->location)));
+ }
+ }
+ break;
+ }
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_partition_ancestors
+ *
+ * Returns list of ancestors all the way up to the root table
+ */
+List *
+get_partition_ancestors(Oid relid)
+{
+ Oid parentOID = InvalidOid;
+ Relation rel;
+
+ rel = heap_open(relid, AccessShareLock);
+ if (rel->rd_rel->relispartition)
+ parentOID = get_partition_parent(relid);
+ heap_close(rel, AccessShareLock);
+
+ if (!OidIsValid(parentOID))
+ return NIL;
+
+ return list_concat(list_make1_oid(parentOID),
+ get_partition_ancestors(parentOID));
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ int i;
+ ListCell *partexprs_item;
+
+ Assert(key);
+
+ if (IsA(bound, PartitionBoundList))
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+ Assert(key->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, list_spec);
+ }
+ else if (IsA(bound, PartitionBoundRange))
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+ Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, range_spec);
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ /* Find all attributes referenced and translate each reference */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+ return list_values_cmp((PartitionKey) arg,
+ (*(const ListValue **) a)->value,
+ (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+ return partition_range_cmp((PartitionKey) arg,
+ (*(const RangePartition **) a)->range,
+ (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with)
+{
+ int i;
+ PartitionList *new_list;
+
+ Assert(listinfo);
+ Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+ new_list = make_list_from_spec(key, list_spec);
+
+ if (new_list->has_null && listinfo->has_null)
+ {
+ *with = listinfo->null_index;
+ return true;
+ }
+
+ for (i = 0; i < new_list->nvalues; i++)
+ {
+ int found;
+
+ /* bsearch a new list's value in listinfo->values */
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ new_list->values[i],
+ key);
+ if (found >= 0)
+ {
+ *with = listinfo->indexes[found];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+ PartitionRangeBound *lower,
+ *upper;
+
+ range = make_range_from_spec(key, range_spec);
+ lower = range->lower;
+ upper = range->upper;
+
+ /*
+ * Range is not empty if one (and only one) of the bounds is infinity.
+ * Both cannot be infinity because of how the syntax is specified.
+ */
+ Assert(!lower->infinite || !upper->infinite);
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper < lower, then it's outright empty. Also if lower = upper
+ * and either is exclusive.
+ */
+ if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+ (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n, int *with)
+{
+ int i;
+ PartitionRange *range;
+
+ /* Create internal representation of range from range_spec */
+ range = make_range_from_spec(key, range_spec);
+
+ Assert(rangeinfo);
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+ {
+ *with = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+ partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+ return true;
+
+ if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+ partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * translate_var_attno
+ *
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand). If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, leave out the test but remove null Const from the list
+ * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The
+ * latter because null-valued expressions does not have the desired
+ * behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(list_spec->values); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ list_spec->values = list_delete_cell(list_spec->values,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = list_spec->values;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ return nulltest1 ? list_make2(nulltest1, opexpr)
+ : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+ list_make2(nulltest2, opexpr),
+ -1))
+ : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ Oid operoid;
+ uint16 strategy;
+ bool need_relabel;
+
+ /*
+ * Handle the case where the partition is bounded on only one side.
+ *
+ * In this case, consider only the first column of the key since
+ * comparison with only the first column would have determined whether
+ * whether a row went into such partition. In other words, it always
+ * follows that -INF < someval and someval < +INF.
+ */
+ if (spec->lower == NIL || spec->upper == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ bool islower,
+ inclusive;
+ NullTest *nulltest;
+
+ if (spec->lower != NIL)
+ {
+ values = spec->lower;
+ islower = true;
+ inclusive = spec->lowerinc;
+ }
+ else
+ {
+ values = spec->upper;
+ islower = false;
+ inclusive = spec->upperinc;
+ }
+
+ /* Left operand */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /* Right operand */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ /* Get the correct btree operator for given strategy */
+ operoid = get_partition_operator(key, 0, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+
+ /* Build the opexpr and return the list containing it and nulltest */
+ return list_make2(nulltest,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->partcollation[0]));
+ }
+
+ /*
+ * We must consider both the lower and upper bounds. Iterate over
+ * columns of the key.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lower, cell2, spec->upper)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Is lower_val = upper_val?
+ */
+
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /*
+ * Yes, build leftop eq lower_val
+ */
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel)
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* No need to constrain further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ *
+ * Use either the column type as the operator datatype or opclass's declared
+ * input type.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ if (need_relabel)
+ *need_relabel = false;
+
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /* Turn that bound into a list of equivalent check quals */
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+ PartitionList *list;
+ ListCell *cell;
+ int i,
+ num_non_null;
+
+
+ list = (PartitionList *) palloc0(sizeof(PartitionList));
+ list->has_null = false;
+
+ /* Never put a null into the values array, flag instead */
+ num_non_null = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->has_null = true;
+ else
+ num_non_null++;
+ }
+
+ list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+ i = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (!val->constisnull)
+ list->values[i++] = datumCopy(val->constvalue,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ }
+
+ list->nvalues = num_non_null;
+
+ return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+
+ range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ range->lower = make_range_bound(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ range->upper = make_range_bound(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+
+ return range;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->infinite = (val == NIL);
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->val[i] = datumCopy(val->constvalue,
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ i++;
+ }
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+ PartitionRange *result;
+
+ result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ result->lower = copy_range_bound(src->lower, key);
+ result->upper = copy_range_bound(src->upper, key);
+
+ return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+ int i;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->lower,
+ r2->ranges[i]->lower) != 0)
+ return false;
+
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->upper,
+ r2->ranges[i]->upper) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ int cmp;
+
+ cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+ if (cmp == 0)
+ cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+ return cmp;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Compare two composite keys
+ */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ val1[i], val2[i]));
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 237d0a2..329d0b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -163,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -482,6 +491,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -594,10 +604,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -672,6 +688,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -1003,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
* but RemoveRelations() can only pass one relkind for a given relation.
@@ -1093,7 +1135,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1471,7 +1514,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1516,8 +1560,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1544,6 +1588,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1579,18 +1635,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1803,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1856,6 +1932,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1885,16 +1964,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1931,8 +2014,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined (unless partition) */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2222,6 +2306,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2452,7 +2541,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2600,11 +2689,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2846,8 +2936,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3126,6 +3219,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3443,6 +3541,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3513,7 +3617,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3762,6 +3872,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3947,7 +4063,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4027,6 +4143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4091,6 +4208,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4406,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4609,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4799,6 +4932,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5321,6 +5459,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5846,6 +6004,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5938,9 +6101,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6329,8 +6494,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7892,7 +8059,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
if (!is_no_inherit_constraint)
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
@@ -7951,8 +8120,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8024,6 +8195,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10191,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10203,12 +10384,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10253,37 +10429,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10318,6 +10468,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10332,16 +10545,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10392,7 +10597,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10410,12 +10615,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10437,14 +10646,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10455,8 +10668,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10471,7 +10686,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10485,7 +10702,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10504,10 +10721,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10554,7 +10775,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10562,7 +10785,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10573,6 +10798,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10586,7 +10812,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10597,6 +10825,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10610,13 +10878,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10625,19 +10891,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10647,7 +10904,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10668,11 +10925,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10681,7 +10947,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10743,7 +11009,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10774,7 +11040,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10786,30 +11052,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
+ drop_parent_dependency(RelationGetRelid(child_rel),
RelationRelationId,
RelationGetRelid(parent_rel));
-
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * ALTER TABLE <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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..1dbfd78 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+ PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+ PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5122,6 +5161,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundList:
+ retval = _copyPartitionBoundList(from);
+ break;
+ case T_PartitionBoundRange:
+ retval = _copyPartitionBoundRange(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a6421d2..03e9e60 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3416,6 +3449,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundList:
+ retval = _equalPartitionBoundList(a, b);
+ break;
+ case T_PartitionBoundRange:
+ retval = _equalPartitionBoundRange(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 417e20a..3a507ca 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundList:
+ _outPartitionBoundList(str, obj);
+ break;
+ case T_PartitionBoundRange:
+ _outPartitionBoundRange(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+ READ_LOCALS(PartitionBoundList);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+ READ_LOCALS(PartitionBoundRange);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2526,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionBoundList();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionBoundRange();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..d40d71c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%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.
@@ -572,7 +580,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
@@ -588,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -602,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2374,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <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:
@@ -2469,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionBoundList *n = makeNode(PartitionBoundList);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2886,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2931,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2953,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2964,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2973,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2994,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -4551,6 +4707,48 @@ CreateForeignTableStmt:
n->options = $14;
$$ = (Node *) n;
}
+ | CREATE FOREIGN TABLE qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $4;
+ n->base.inhRelations = list_make1($7);
+ n->base.tableElts = $8;
+ n->base.partbound = (Node *) $9;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = false;
+ /* FDW-specific data */
+ n->servername = $11;
+ n->options = $12;
+ $$ = (Node *) n;
+ }
+ | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $7->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $7;
+ n->base.inhRelations = list_make1($10);
+ n->base.tableElts = $11;
+ n->base.partbound = (Node *) $12;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = true;
+ /* FDW-specific data */
+ n->servername = $14;
+ n->options = $15;
+ $$ = (Node *) n;
+ }
;
/*****************************************************************************
@@ -11156,6 +11354,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13755,6 +13954,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13801,6 +14001,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13843,6 +14044,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
-
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..f565a5d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +92,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partspec)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -359,6 +372,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -898,6 +912,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1117,6 +1132,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3059,334 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+ PartitionBoundList *list, *result_list;
+ PartitionBoundRange *range, *result_range;
+
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (!IsA(bound, PartitionBoundList))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionBoundList *) bound;
+ result_list = makeNode(PartitionBoundList);
+
+ foreach(cell, list->values)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ char *colname;
+
+ if (!IsA(bound, PartitionBoundRange))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionBoundRange *) bound;
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionBoundRange);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower && list_length(range->lower) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (range->lower && list_length(range->lower) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ if (range->upper && list_length(range->upper) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (range->upper && list_length(range->upper) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ if (range->lower)
+ {
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ colname = deparse_expression((Node *) list_nth(partexprs, i),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ colname = deparse_expression((Node *) list_nth(partexprs, i),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e80ff80..1666233 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->bounds != NULL)
+ {
+ if (pdesc2->bounds == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+ pdesc1->nparts))
+ return false;
+ }
+ else if (pdesc2->bounds != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2609,13 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ {
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+ SWAPFIELD(MemoryContext, rd_pdcxt);
+ }
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..c3ad626 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..ea7806e
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection bounds; /* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_partition_ancestors(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundList,
+ T_PartitionBoundRange,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ada75bd..15e8d3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -735,6 +736,40 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1562,7 +1597,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1788,7 +1825,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR: source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 36f487a..e0181c4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -437,3 +437,191 @@ Table "public.describe_list_key"
Partition key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+ERROR: "part_a" is a partition of "parted"
+HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 5a0d933..7255690 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,142 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
--
1.7.1
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-7.patch (20.6K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-7.patch)
download | inline diff:
From f8560f1ff9139cff0ec7536fc195fdb4bc98a950 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 85 +++++++++++++++++++++----
src/test/regress/expected/create_table.out | 39 +++++++++++
src/test/regress/sql/create_table.sql | 12 ++++
7 files changed, 393 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 51e175e..c76ebb1 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundList:
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) 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_PartitionBoundRange:
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) 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 e310b63..4788018 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6141,6 +6141,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15321,6 +15378,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 */
@@ -15349,8 +15417,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;
@@ -15379,7 +15450,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");
}
@@ -15444,15 +15516,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 (");
@@ -15622,6 +15701,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 db6dc5c..9834599 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e0181c4..19257d6 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -595,6 +595,45 @@ 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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- 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 7255690..fecae9b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -551,6 +551,18 @@ 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partition cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-7.patch (16.2K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-7.patch)
download | inline diff:
From 85a3d3f401a53f412fe3f0b82a4e3a2a0bf03c5b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.
If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
src/backend/optimizer/plan/initsplan.c | 17 ++-
src/backend/optimizer/prep/prepunion.c | 282 +++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
3 files changed, 224 insertions(+), 84 deletions(-)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *rte = root->simple_rte_array[rti];
- if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+ if (brel == NULL)
+ continue;
+
+ /*
+ * If an "other rel" RTE is a "partitioned table", we must propagate
+ * the lateral info inherited all the way from the root parent to its
+ * children. That's because the children are not linked directly with
+ * the root parent via AppendRelInfo's unlike in case of a regular
+ * inheritance set (see expand_inherited_rtentry()). Failing to
+ * do this would result in those children not getting marked with the
+ * appropriate lateral info.
+ */
+ if (brel->reloptkind != RELOPT_BASEREL &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
continue;
if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /*
+ * Do not flatten the inheritance hierarchy if partitioned table, unless
+ * this is the result relation.
+ */
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+ rti != root->parse->resultRelation)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
- */
- if (childOID != parentOID)
- {
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
- }
-
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (oldrc)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-7.patch (30.2K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-7.patch)
download | inline diff:
From 017789dfcd8fd1c8634f0c2b71857ad6b3706a20 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 5790edc..5b0e8cf 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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-7.patch (8.0K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-7.patch)
download | inline diff:
From 17652ea80aad319bd505519e0ab7768e3652f3ab Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 206 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 211 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 05e63c3..402eb9e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
PartitionRange *range;
} RangePartition;
+/*
+ * 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_parts 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 RelationBuildPartitionDesc() */
static int32 list_value_cmp(const void *a, const void *b, void *arg);
static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
/* Support RelationGetPartitionQual() */
static List *generate_partition_qual(Relation rel, bool recurse);
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -774,6 +833,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1423,6 +1529,106 @@ generate_partition_qual(Relation rel, bool recurse)
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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 ea7806e..1ecd5d6 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -56,4 +57,8 @@ extern List *get_partition_ancestors(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-7.patch (40.6K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-7.patch)
download | inline diff:
From 22253a61d816157399060847dcfec1ccfe22edc6 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 343 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 ++++-
src/backend/executor/nodeModifyTable.c | 123 +++++++++++
src/backend/optimizer/plan/createplan.c | 59 ++++++
src/backend/optimizer/util/plancat.c | 20 ++-
src/backend/parser/analyze.c | 8 +
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, 907 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 402eb9e..873ee0d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
PartitionRangeBound *b2);
static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
/*
* RelationBuildPartitionDesc
@@ -1545,7 +1560,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;
@@ -1629,6 +1644,267 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ ListInfo *listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->bounds->listinfo != NULL);
+ listinfo = pdesc->bounds->listinfo;
+
+ if (isnull && listinfo->has_null)
+ return listinfo->null_index;
+ else if (!isnull)
+ {
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ value,
+ key);
+ if (found >= 0)
+ return listinfo->indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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);
+ Assert(pdesc->bounds->rangeinfo != NULL);
+
+ return bsearch_ranges(key, pdesc->nparts,
+ pdesc->bounds->rangeinfo, tuple);
+}
+
/* List partition related support functions */
/*
@@ -1976,3 +2252,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
return result;
}
+
+/*
+ * bsearch_ranges
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = n - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (rangeinfo->ranges[idx]->upper->infinite)
+ {
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+ return idx;
+
+ break;
+ }
+ else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+ {
+ if (rangeinfo->ranges[idx]->lower->infinite)
+ return idx;
+
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->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, PartitionRangeBound *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, PartitionRangeBound *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 44c273c..8c7e5f2 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.
@@ -1421,6 +1427,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1716,6 +1810,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1729,6 +1825,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);
}
@@ -2279,6 +2392,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2299,7 +2413,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2407,6 +2522,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2434,6 +2550,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;
@@ -2455,10 +2572,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 */
@@ -2510,6 +2663,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 */
@@ -2530,7 +2708,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)
@@ -2560,7 +2747,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),
@@ -2580,6 +2768,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2593,7 +2787,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 329d0b4..3e9b171 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1256,6 +1256,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 5b0e8cf..cb47035 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,79 @@ 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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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 +2063,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 47158f6..7364211 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6152,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 (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ 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 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 1ecd5d6..b6607d8 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/rel.h"
@@ -61,4 +63,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 697c90f..6e51773 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"
@@ -1141,6 +1142,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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-7.patch (24.7K, ../../377bc541-2108-4500-4b87-1b5936553555@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-7.patch)
download | inline diff:
From a8f7ad4e6557da09378f22699480f0c1d8657766 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 f43352c..8084029 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-04 17:12 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-10-04 17:12 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Even if we leave the empty relfilenode around for now -- in the long
>> run I think it should die -- I think we should prohibit the creation
>> of subsidiary object on the parent which is only sensible if it has
>> rows - e.g. indexes. It makes no sense to disallow non-inheritable
>> constraints while allowing indexes, and it could box us into a corner
>> later.
>
> I agree. So we must prevent from the get-go the creation of following
> objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):
>
> * Indexes
> * Row triggers (?)
Hmm, do we ever fire triggers on the parent for operations on a child
table? Note this thread, which seems possibly relevant:
https://www.postgresql.org/message-id/flat/cd282adde5b70b20c57f53bb9ab75e27%40biglumber.com
We certainly won't fire the parent's per-row triggers ever, so those
should be prohibited. But I'm not sure about per-statement triggers.
> In addition to preventing creation of these objects, we must also teach
> commands that directly invoke heapam.c to skip such relations.
I'm don't think that's required if we're not actually getting rid of
the relfilenode.
> In this case, relkind refers to the command viz. DROP <ObjectType/relkind>
> <relname>. It can never be RELKIND_PARTITIONED_TABLE, so the above will
> be equivalent to leaving things unchanged. Anyway I agree the suggested
> style is better, but I assume you meant:
>
> if (classform->relkind == RELKIND_PARTITIONED_TABLE)
> expected_relkind = RELKIND_RELATION;
> else
> expected_relkind = classform->relkind;
>
> if (relkind != expected_relkind)
> DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
Uh, yeah, probably that's what I meant. :-)
> Thanks for the explanation. I added a new field to the catalog called
> partcollation.
>
> That means a couple of things - when comparing input key values (consider
> a new tuple being routed) to partition bounds using the btree compare
> function, we use the corresponding key column's partcollation. The same
> is also used as inputcollid of the OpExpr (or ScalarArrayOpExpr) in the
> implicitly generated CHECK constraints.
Check.
> However, varcollid of any Var nodes in the above expressions is still the
> corresponding attributes' attcollation.
I think that's OK.
> Needless to say, a query-specified qualification clause must now include
> the same collate clause as used for individual partition columns (or
> expressions) for the constraint exclusion to work as intended.
Hopefully this is only required if the default choice of collation
wouldn't be correct anyway.
>> + <entry><structfield>partexprbin</structfield></entry>
>>
>> Is there any good reason not to do partexprbin -> partexpr throughout the patch?
>
> Agreed. Though I used partexprs (like indexprs).
Oh, good idea.
>> case EXPR_KIND_TRIGGER_WHEN:
>> return "WHEN";
>> + case EXPR_KIND_PARTITION_KEY:
>> + return "partition key expression";
>>
>> I think you should say "PARTITION BY" here. See the function header
>> comment for ParseExprKindName.
>
> Used "PARTITION BY". I was trying to mimic EXPR_KIND_INDEX_EXPRESSION,
> but this seems to work better. Also, I renamed EXPR_KIND_PARTITION_KEY to
> EXPR_KIND_PARTITION_EXPRESSION.
>
> By the way, a bunch of error messages say "partition key expression". I'm
> assuming it's better to leave them alone, that is, not rewrite them as
> "PARTITION BY expressions".
I think that is fine. ParseExprKindName is something of a special case.
Reviewing 0002:
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ prettyFlags)));
Why bother with the variable?
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
Comment mentions getting two things, but code only gets one.
+ partexprs = (List *) stringToNode(exprsString);
if (!IsA(partexprs, List)) elog(ERROR, ...); so as to guard against
corrupt catalog contents.
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
default: elog(ERROR, "unexpected partition strategy: %d", (int)
form->partstrat);
Also, why not case PARTITION_STRATEGY_LIST: instead of case 'l': and
similarly for 'r'?
@@ -5296,7 +5301,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 "
I think if you test it you'll find this breaks dumps from older server
versions. You've got to add a new version of the SQL query for 10+,
and then update the 9.6, 9.5, 9.4, 9.3, 9.1-9.2, 9.0, 8.4, 8.2-8.3,
8.0-8.1, 7.3-7.4, 7.2, 7.1, and pre-7.1 queries to return a dummy NULL
value for that column. Alternatively, you can add the new version for
10.0, leave the older queries alone, and then adjust the code further
down to cope with i_partkeydef == -1 (see i_checkoption for an
example).
gettext_noop("table"),
+ gettext_noop("table"),
Add a comment like /* partitioned table */ on the same line.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-05 01:50 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 3 replies; 329+ messages in thread
From: Robert Haas @ 2016-10-05 01:50 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> [ latest patch set ]
Reviewing 0003:
+ This form attaches an existing table (partitioned or otherwise) as
(which might itself be partitioned)
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
The partition bound specification must correspond to the partitioning
method and partitioning key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
The table to be attached must have all of the same columns as the
target table and no more; moreover, the column types must also match.
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO
INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
Why all of these requirements? We could instead perform a scan to
validate that the constraints are met. I think the way this should
work is:
1. ATTACH PARTITION works whether matching NOT NULL and CHECK
constraints are present or not.
2. If all of the constraints are present, and a validated constraint
matching the implicit partitioning constraint is also present, then
ATTACH PARTITION does not scan the table to validate constraints;
otherwise, it does.
3. NO VALIDATE is not an option.
+ Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
+ <literal>FOREIGN KEY</literal> constraints are not considered, but that
+ might change in the future.
Really? Changing that sounds impractical to me.
+ This form detaches specified partition of the target table. The
+ detached partition continues to exist as a standalone table with no ties
+ remaining with the target table.
continues to exist as a standalone table, but no longer has any ties
to the table from which it was detached.
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
You don't really need to say this, I think. All of the properties of
the detached table are retained, not only its partitioning status.
You wouldn't like it if I told you to document "note that if a
partition being detached is unlogged, it will still be unlogged".
To add the table as a new child of a parent table, you must own the
- parent table as well.
+ parent table as well. That applies to both adding the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To add the table as a new child of a parent table, or as a new
partition of a partitioned table, you must own the parent table as
well.
+ The name of the table to attach as a new partition to or
detach from this table.
s/to or/or to/
+ <literal>NO VALIDATE</> option is spcified.
Typo, but see comments above about nuking this altogether.
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
This is a hairy explanation. I suggest that the documentation say
this (and that the code implement it): A nonrecursive DROP TABLE
command will fail for a partitioned table, because all partitions of a
table must have the same columns as the partitioning root.
- that are not marked <literal>NO INHERIT</>.
+ that are not marked <literal>NO INHERIT</> which are unsupported if
+ the table is a partitioned table.
I think you can omit this hunk.
+ 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.
I'd delete the sentence beginning with "However".
+ 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</>:
s/, that is/ as a/
+<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> }
I think you can inline the definitions of list_spec and range_spec
here instead of making them separate productions, and I think that
would be preferable.
FOR VALUES { IN ( <replaceable
class="PARAMETER">expression</replaceable> [, ...] ) |
START <replaceable class="PARAMETER">lower-bound</replaceable> [
INCLUSIVE | EXCLUSIVE ] END <replaceable
class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
}
+ parent table (name optionally schema-qualified).
Parenthetical phrase isn't needed.
+ 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.
The partition bound specification must correspond to the partitioning
method and partitioning key of the parent table, and must not overlap
with any existing partition of that parent.
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
Surely not. You can't "override" an inherited constraint or an
inherited default. The child may have extra constraints not present
in the parent, and may have different defaults when it is directly
targeted by an insertion, but it can't possibly override the parent
defaults.
+ 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.
The first part of this seems right, but then what's going on with the
reference to constraints in the previous sentence? (See previous
review comment.) The second sentence I would delete (but see below).
+ <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>
How about: Rows inserted into a partitioned table will be
automatically routed to the correct partition. If no suitable
partition exists, an error will occur.
+ <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>
How about: A partition must have the same column names and types as
the table of which it is a partition. Therefore, modifications the
column names or types of the partitioned table will automatically
propagate to all children, as will operations such as TRUNCATE which
normally affect a table and all of its inheritance children. It is
also possible to TRUNCATE a partition individually, just as for an
inheritance child.
Insisting that you can't drop a child without detaching it first seems
wrong to me. If I already made this comment and you responded to it,
please point me back to whatever you said. However, my feeling is
this is flat wrong and absolutely must be changed.
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
Something about this seems fishy. In StoreRelCheck(), you disallow
the addition of a NO INHERIT constraint on a partition, but here you
seem happy to accept it and ignore the NO INHERIT property. That
doesn't seem consistent. We should adopt a consistent policy about
what to do about such constraints, and I submit that throwing an error
is better than silently changing things, unless you have some reason
for doing that which I'm not seeing. Anyway, we should have the same
behavior in both cases.
We should also standardize on what value of conislocal and coninhcount
children end up with; presumably the latter should be 1, but I'm not
sure if the former should be true or false. In either case, anything
that can vary between children probably needs to be dumped, so let's
enforce that it doesn't so we don't have to dump it. I'm not sure
whether the code here achieves those objectives, though, and note the
comment in the function header about making sure the logic here
matches MergeConstraintsIntoExisting.
I think the overriding principle here should be: If you attach a table
as a partition, it must not be part of a standard inheritance
hierarchy, and it must not be a partition of any other table. It can,
however, be partitioned itself. If you later detach a partition, it
ends up as a standalone table with a copy of each constraint it had as
a partition - probably including the implicit partition constraint.
The DBA can drop those constraints if they're not wanted.
I wonder if it's really a good idea for the partition constraints to
be implicit; what is the benefit of leaving those uncatalogued?
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
This seems like an odd design. First, when you have a pointer to
either of two things, the normal tool for that in C would be a union,
not a struct. Second, in PostgreSQL we typically handle that by making
both of the things nodes and then you can use IsA() or switch on
nodeTag() to figure out what you've got. Third, the only place this
is used at least in 0003 is as part of PartitionDescData, which only
has 3 members, so if you were going to do it with 2 members, you could
just include these two members directly. Considering all of the
foregoing, I'd suggest making this a union and including partstrategy
in PartitionDescData.
I think that the names ListInfo and RangeInfo are far too generic for
something that's specific to partitioning.
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
This representation doesn't seem optimal, because in most cases the
lower bound of one partition will be the upper bound of the next. I
suggest that you flatten this representation into a single list of
relevant bounds, each flagged as to whether it is exclusive and
whether it is finite; and a list with one more element of bounds. For
example, suppose the partition bounds are [10, 20), [20, 30), (30,
40), and [50, 60). You first construct a list of all of the distinct
bounds, flipping inclusive/exclusive for the lefthand bound in each
case. So you get:
10 EXCLUSIVE
20 EXCLUSIVE
30 EXCLUSIVE
30 INCLUSIVE
40 EXCLUSIVE
50 EXCLUSIVE
60 EXCLUSIVE
When ordering items for this list, if the same item appears twice, the
EXCLUSIVE copy always appears before INCLUSIVE. When comparing
against an EXCLUSIVE item, we move to the first half of the array if
we are searching for a value strictly less than that value; when
comparing against an INCLUSIVE item, we move to the first half of the
array if we are searching for a value less than or equal to that
value.
This is a list of seven items, so a binary search will return a
position between 0 (less than all items) and 7 (greater than all
items). So we need a list of 8 partition mappings, which in this case
will look like this: -1, 0, 1, -1, 2, -1, 3, -1.
In this particular example, there are only two adjacent partitions, so
we end up with 7 bounds with this representation vs. 8 with yours, but
in general I think the gains will be bigger. If you've got 1000
partitions and they're all adjacent, which is likely, you store 1000
bounds instead of 2000 bounds by doing it this way.
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
Why? How about "Because this function assumes that the relation whose
OID is passed as an argument will have precisely one parent, it should
only been called when it is known that the relation is a partition."
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
It can't really be safe to do this one attribute number at a time, or
even if by some good fortune it can't be broken, it at least it seems
extremely fragile. Suppose that you translate 0 -> 3 and then 3 -> 5;
now the result is garbage. It's not very efficient to do this one
attno at a time, either.
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able
to drop it.")));
+
RangeVarCallbackForDropRelation should do only minimal sanity checks;
defer this until after we have a relation lock.
I didn't get all the way through this patch, so this is a pretty
incomplete review, but it's late here and I'm out of steam for
tonight. Some general comments:
1. I think that this patch seems to introduce an awful lot of new
structures with confusingly similar names and purposes:
PartitionBoundList, PartitionBoundRange, ListInfo, RangeInfo,
PartitionRange, PartitionList, ListValue, RangePartition. You've got
4 different structures here that all have "Partition" and "Range" in
the name someplace, including both PartitionRange and RangePartition.
Maybe there's no way around that kind of duplication; after all there
are quite a few moving parts here. But it seems like it would be good
to try to simplify it.
2. I'm also a bit concerned about the fairly large amount of
apparently-boilerplate code in partition.c, all having to do with how
we create all of these data structures and translate between different
forms of them. I haven't understood that stuff thoroughly enough to
have a specific idea about how we might be able to get rid of any of
it, and maybe there's no way. But that too seems like a topic for
futher investigation. One idea is that maybe some of these things
should be nodes that piggyback on the existing infrastructure in
src/backend/nodes instead of inventing a new way to do something
similar.
3. There are a lot of places where you have separate code for the
range and list partitioning cases, and I'm suspicious that there are
ways that code could be unified. For example, with list partitioning,
you have a bunch of Datums which represent the specific values that
can appear in the various partitions, and with range partitioning, you
have a bunch of Datums that represent the edges of partitions. Well,
if you used the same array for both purposes, you could use the same
code to copy it. That would involve flattening away a lot of the
subsidiary structure under PartitionDescData and pulling up stuff that
is buried lower down into the main structure, but I think that's
likely a good idea anyway - see also point #1.
4. I'm somewhat wondering if we ought to just legislate that the lower
bound is always inclusive and the upper bound is always exclusive.
The decision to support both inclusive and exclusive partition bounds
is responsible for an enormous truckload of complexity in this patch,
and I have a feeling it is also going to be a not-infrequent cause of
user error.
5. I wonder how well this handles multi-column partition keys. You've
just got one Datum flag and one is-finite flag per partition, but I
wonder if you don't need to track is-finite on a per-column basis, so
that you could partition on (a, b) and have the first partition go up
to (10, 10), the second to (10, infinity), the third to (20, 10), the
fourth to (20, infinity), and the last to (infinity, infinity). FWIW,
Oracle supports this sort of thing, so perhaps we should, too. On a
related note, I'm not sure it's going to work to treat a composite
partition key as a record type. The user may want to specify a
separate opfamily and collation for each column, not just inherit
whatever the record behavior is. I'm not sure if that's what you are
doing, but the relcache structures don't seem adapted to storing one
Datum per partitioning column per partition, but rather just one Datum
per partition, and I'm not sure that's going to work very well.
Thanks for working on this.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-05 04:47 Petr Jelinek <petr@2ndquadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
2 siblings, 0 replies; 329+ messages in thread
From: Petr Jelinek @ 2016-10-05 04:47 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 05/10/16 03:50, Robert Haas wrote:
> On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> [ latest patch set ]
>
> Reviewing 0003:
>
> + with matching types and no more. Also, it must have all the matching
> + constraints as the target table. That includes both <literal>NOT NULL</>
> + and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
> + constraint of the table being attached is marked <literal>NO
> INHERIT</literal>,
> + the command will fail; such constraints must either be dropped or
> + recreated without the <literal>NO INHERIT</literal> mark.
>
> Why all of these requirements? We could instead perform a scan to
> validate that the constraints are met. I think the way this should
> work is:
I think it's survivable limitation in initial version, it does not seem
to me like there is anything that prevents improving this in some follow
up patch.
>
> + Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
> + <literal>FOREIGN KEY</literal> constraints are not considered, but that
> + might change in the future.
>
> Really? Changing that sounds impractical to me.
>
Which part of that statement?
>
> + Note that if a partition being detached is itself a partitioned table,
> + it continues to exist as such.
>
> You don't really need to say this, I think. All of the properties of
> the detached table are retained, not only its partitioning status.
> You wouldn't like it if I told you to document "note that if a
> partition being detached is unlogged, it will still be unlogged".
>
I think this is bit different though, it basically means you are
detaching whole branch from the rest of the partitioning tree, not just
that one partition. To me that's worth mentioning to avoid potential
confusion.
--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-05 07:57 Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Rajkumar Raghuwanshi @ 2016-10-05 07:57 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Oct 4, 2016 at 1:32 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
Attached updated patches.
>
> Thanks,
> Amit
>
Hi,
I observed, when creating foreign table with range partition, data is not
inserting into specified partition range. below are steps to reproduce.
CREATE EXTENSION postgres_fdw;
CREATE SERVER pwj_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname
'postgres', port '5432',use_remote_estimate 'true');
CREATE USER MAPPING FOR PUBLIC SERVER pwj_server;
CREATE TABLE test_range (a int) PARTITION BY RANGE(a);
CREATE TABLE test_range_p1 (a int);
CREATE FOREIGN TABLE ft_test_range_p1 PARTITION OF test_range FOR VALUES
START (1) END (10) SERVER pwj_server OPTIONS (TABLE_NAME 'test_range_p1');
CREATE TABLE test_range_p2 (a int);
CREATE FOREIGN TABLE ft_test_range_p2 PARTITION OF test_range FOR VALUES
START (20) END (30) SERVER pwj_server OPTIONS (TABLE_NAME 'test_range_p2');
CREATE TABLE test_range_p3 (a int);
CREATE FOREIGN TABLE ft_test_range_p3 PARTITION OF test_range FOR VALUES
START (10) END (20) SERVER pwj_server OPTIONS (TABLE_NAME 'test_range_p3');
postgres=# INSERT INTO test_range (a) values (5),(25),(15);
INSERT 0 3
postgres=# select tableoid::regclass, * from test_range;
tableoid | a
------------------+----
ft_test_range_p1 | 5
ft_test_range_p2 | 15
ft_test_range_p3 | 25
(3 rows)
--Here ft_test_range_p2 is created for range 20-30 having value 15.
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-05 08:59 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-05 08:59 UTC (permalink / raw)
To: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Hi,
On 2016/10/05 16:57, Rajkumar Raghuwanshi wrote:
> I observed, when creating foreign table with range partition, data is not
> inserting into specified partition range. below are steps to reproduce.
>
> [ ... ]
>
> postgres=# INSERT INTO test_range (a) values (5),(25),(15);
> INSERT 0 3
>
> postgres=# select tableoid::regclass, * from test_range;
> tableoid | a
> ------------------+----
> ft_test_range_p1 | 5
> ft_test_range_p2 | 15
> ft_test_range_p3 | 25
> (3 rows)
>
> --Here ft_test_range_p2 is created for range 20-30 having value 15.
Thanks a lot for testing.
That's a bug. I found that it is caused by the FDW plans getting paired
with the wrong result relations during ExecInitModifyTable() initialization.
I will include a fix for the same in the patch set that I will be sending
soon in reply to Robert's review comments on patch 0002 [1].
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoY1aQ5iPz0S2GBJw4YUR1Z2Qg5iKUf8YJSo2Ctya4ZmNg%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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-06 07:14 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 3 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-06 07:14 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/05 2:12, Robert Haas wrote:
> On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> Even if we leave the empty relfilenode around for now -- in the long
>>> run I think it should die -- I think we should prohibit the creation
>>> of subsidiary object on the parent which is only sensible if it has
>>> rows - e.g. indexes. It makes no sense to disallow non-inheritable
>>> constraints while allowing indexes, and it could box us into a corner
>>> later.
>>
>> I agree. So we must prevent from the get-go the creation of following
>> objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):
>>
>> * Indexes
>> * Row triggers (?)
>
> Hmm, do we ever fire triggers on the parent for operations on a child
> table? Note this thread, which seems possibly relevant:
>
> https://www.postgresql.org/message-id/flat/cd282adde5b70b20c57f53bb9ab75e27%40biglumber.com
The answer to your question is no.
The thread you quoted discusses statement-level triggers and the
conclusion is that they don't work as desired for UPDATE and DELETE on
inheritance tables. As things stand, only UPDATE or DELETE on the parent
affects the child tables and it's proposed there that the statement-level
triggers on the parent and also on any child tables affected should be
fired in that case.
Currently, INSERT doesn't have that problem because it only ever affects
the parent table. That changes with tuple routing though. I am not sure
if the aforementioned proposed behavior should be applied in this case.
That is to say, if we have INSERT INTO parent, only the parent's
statement-level triggers should be fired. The partition into which the
tuple is routed will have only its row-level triggers fired.
The proposed TODO item has been left untouched for many years now, so it
is perhaps not such a big pain point after all (or perhaps some standard
noncompliance). I may be wrong though.
> We certainly won't fire the parent's per-row triggers ever, so those
> should be prohibited. But I'm not sure about per-statement triggers.
Agree about the row-level triggers. I think per-statement triggers are
alright to allows on parent tables from the POV of the original topic we
were discussing - ie, per-statement triggers do not depend on the physical
addressing capabilities of the relation on which they are defined whereas
row-levels triggers do in some cases.
So I changed 0001 so that indexes and row triggers are disallowed on
partitioned tables.
>> In addition to preventing creation of these objects, we must also teach
>> commands that directly invoke heapam.c to skip such relations.
>
> I'm don't think that's required if we're not actually getting rid of
> the relfilenode.
Oops that line got sent after all, I had convinced myself to remove that
line from the email. :) Agreed, no need to do that just yet.
>> Needless to say, a query-specified qualification clause must now include
>> the same collate clause as used for individual partition columns (or
>> expressions) for the constraint exclusion to work as intended.
>
> Hopefully this is only required if the default choice of collation
> wouldn't be correct anyway.
Sorry, I'm not sure I understand what you said - specifically what would
"wrong" mean in this case? Parser error? Run-time error like in varstr_cmp()?
What I meant to say is that predicate OpExpr's generated by the
partitioning code will be using the corresponding partition column's
partcollation as its inputcollid (and the Var will be wrapped by a
RelabelType). Here partcollation is a known valid alternative collation
that is applied when partitioning the input data, overriding any default
collation of the column's type or the collation specified for the column
when creating the table. During assign_query_collations(), a qual clause
OpExpr's variable will get assigned a collation that is either the
column/expressions's attcollation/exprCollationor explicitly specified
collation using a COLLATE clause. If variable collation assigned thusly
doesn't match the partitioning collation, then the predicate OpExpr's and
the query clause OpExpr's variable expressions will fail to match using
equal(), so constraint exclusion (specifically, operator_predicate_proof()
bails out) will fail. Of course, going ahead with constraint exclusion
ignoring the said collation mismatch would be wrong anyway.
> Reviewing 0002:
>
> + prettyFlags = PRETTYFLAG_INDENT;
> + PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
> + prettyFlags)));
>
> Why bother with the variable?
Leftover, removed.
> + /* Must get partclass, and partexprs the hard way */
> + datum = SysCacheGetAttr(PARTEDRELID, tuple,
> + Anum_pg_partitioned_table_partclass, &isnull);
> + Assert(!isnull);
> + partclass = (oidvector *) DatumGetPointer(datum);
>
> Comment mentions getting two things, but code only gets one.
Fixed. Further code uses the "hard way" to get partexprs but it's got a
separate comment anyway.
>
> + partexprs = (List *) stringToNode(exprsString);
>
> if (!IsA(partexprs, List)) elog(ERROR, ...); so as to guard against
> corrupt catalog contents.
Done as follows:
partexprs = (List *) stringToNode(exprsString);
if (!IsA(partexprs, List))
elog(ERROR, "unexpected node type found in partexprs: %d",
(int) nodeTag(partexprs));
> + switch (form->partstrat)
> + {
> + case 'l':
> + appendStringInfo(&buf, "LIST");
> + break;
> + case 'r':
> + appendStringInfo(&buf, "RANGE");
> + break;
> + }
>
> default: elog(ERROR, "unexpected partition strategy: %d", (int)
> form->partstrat);
>
> Also, why not case PARTITION_STRATEGY_LIST: instead of case 'l': and
> similarly for 'r'?
Done and done.
> @@ -5296,7 +5301,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 "
>
> I think if you test it you'll find this breaks dumps from older server
> versions. You've got to add a new version of the SQL query for 10+,
> and then update the 9.6, 9.5, 9.4, 9.3, 9.1-9.2, 9.0, 8.4, 8.2-8.3,
> 8.0-8.1, 7.3-7.4, 7.2, 7.1, and pre-7.1 queries to return a dummy NULL
> value for that column. Alternatively, you can add the new version for
> 10.0, leave the older queries alone, and then adjust the code further
> down to cope with i_partkeydef == -1 (see i_checkoption for an
> example).
You're quite right. Fixed using this last method.
> gettext_noop("table"),
> + gettext_noop("table"),
>
> Add a comment like /* partitioned table */ on the same line.
Done.
Attached revised patches. Also, includes a fix for an issue reported by
Rajkumar Raghuwanshi [1] which turned out to be a bug in one of the later
patches. I will now move on to addressing the comments on patch 0003.
Thanks a lot for the review!
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/5dded2f1-c7f6-e7fc-56b5-23ab59495e4b@lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-8.patch (113.3K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-8.patch)
download | inline diff:
From 35c961198e923f826a851f79bc7c9b7f18c77666 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 160 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 9 +-
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 | 434 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 +++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 257 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 5 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 36 +++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 66 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 137 +++++++++
51 files changed, 1915 insertions(+), 49 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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>partcollation</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 collation to use for partitioning.
+ </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>partattrs</>. 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..893c899 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,41 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partitioning key</firstterm>
+ for the table. When using range partitioning, the partitioning key can
+ include multiple columns or expressions, but for list partitioning, the
+ partitioning key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is routed
+ to a partition based on the value of columns or expressions in the
+ partition key. If no existing partition matches the values in the new
+ row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..2994cd0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1103,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1137,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1182,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1358,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1806,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2044,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -2983,3 +3006,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * 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,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition key, opclass info into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references which will count as self-dependency items; in this case,
+ * the depender is the table itself (there is no such thing as partition
+ * key object).
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 457c9bb..9801f0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9f90e62 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1256,7 +1259,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..230a7ad 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..237d0a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process and store partition key information, if any */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * 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 *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (is_expr)
+ *is_expr = false;
+ if (attnum == partattno)
+ return true;
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ if (is_expr)
+ *is_expr = true;
+
+ /* Find all attributes referenced */
+ 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;
+
+ if (attno == attnum)
+ return true;
+ }
+ partexprs_item = lnext(partexprs_item);
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5840,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 +5885,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 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8017,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7892,6 +8048,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7907,7 +8076,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8934,6 +9104,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9567,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9990,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10190,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10068,6 +10246,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11446,6 +11631,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12081,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12235,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12247,236 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partition strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears 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_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..133776d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1112,6 +1120,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1227,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 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_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +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_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e80ff80 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;
/*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..95959c0
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..ada75bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ 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 *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name ('list', 'range', etc.)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ List *partexprs; /* partition key expressions, if any */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation */
+ Oid *partcollation;
+
+ /* Type information of partition attributes */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..5f31540 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ 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 list partition using more than one column
+-- 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: set-returning functions are not allowed in partition key expression
+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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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
+-- 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 a btree operator class or define a default btree operator class for the data 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 a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..48a660f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ 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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+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();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-8.patch (26.2K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-8.patch)
download | inline diff:
From f555a191b3cfb74c472e721f5a1eb5ccf2f7d9c3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 146 ++++++++++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 134 ++++++++++++++++++++++++-
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, 366 insertions(+), 24 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..82f03ea 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_table.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,150 @@ 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 relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ 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);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ /*
+ * 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 299e887..8aa615b 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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,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");
@@ -5235,7 +5240,108 @@ getTables(Archive *fout, int *numTables)
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
- if (fout->remoteVersion >= 90600)
+ if (fout->remoteVersion >= 100000)
+ {
+ PQExpBuffer acl_subquery = createPQExpBuffer();
+ PQExpBuffer racl_subquery = createPQExpBuffer();
+ PQExpBuffer initacl_subquery = createPQExpBuffer();
+ PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+ PQExpBuffer attacl_subquery = createPQExpBuffer();
+ PQExpBuffer attracl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ *
+ * Left join to detect if any privileges are still as-set-at-init, in
+ * which case we won't dump out ACL commands for those.
+ */
+
+ buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+ initracl_subquery, "c.relacl", "c.relowner",
+ "CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::\"char\"",
+ dopt->binary_upgrade);
+
+ buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+ attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+ dopt->binary_upgrade);
+
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "%s AS relacl, %s as rrelacl, "
+ "%s AS initrelacl, %s as initrrelacl, "
+ "c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relrowsecurity, c.relforcerowsecurity, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "c.relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "tc.reloptions AS toast_reloptions, "
+ "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = at.attnum)"
+ "WHERE at.attrelid = c.oid AND ("
+ "%s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL"
+ "))"
+ "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 "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = 0) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ acl_subquery->data,
+ racl_subquery->data,
+ initacl_subquery->data,
+ initracl_subquery->data,
+ username_subquery,
+ attacl_subquery->data,
+ attracl_subquery->data,
+ attinitacl_subquery->data,
+ attinitracl_subquery->data,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_TABLE);
+
+ destroyPQExpBuffer(acl_subquery);
+ destroyPQExpBuffer(racl_subquery);
+ destroyPQExpBuffer(initacl_subquery);
+ destroyPQExpBuffer(initracl_subquery);
+
+ destroyPQExpBuffer(attacl_subquery);
+ destroyPQExpBuffer(attracl_subquery);
+ destroyPQExpBuffer(attinitacl_subquery);
+ destroyPQExpBuffer(attinitracl_subquery);
+ }
+ else if (fout->remoteVersion >= 90600)
{
PQExpBuffer acl_subquery = createPQExpBuffer();
PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5884,6 +5990,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)
{
@@ -5954,6 +6061,10 @@ 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));
+ if (i_partkeydef == -1 || PQgetisnull(res, i, i_partkeydef))
+ tblinfo[i].partkeydef = NULL;
+ else
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5998,7 +6109,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6100,7 +6213,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);
@@ -15456,6 +15572,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15516,6 +15635,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15534,7 +15654,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15751,6 +15872,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..e57d78e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 5f31540..36f487a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -411,3 +411,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: 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 48a660f..5a0d933 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-8.patch (204.9K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-8.patch)
download | inline diff:
From 8ff8169bbc9f2e20a3c1711c19b9e7fe5e531d92 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 111 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 28 +
doc/src/sgml/ref/create_table.sgml | 105 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 33 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1760 ++++++++++++++++++++++++++++
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 774 ++++++++++--
src/backend/nodes/copyfuncs.c | 48 +
src/backend/nodes/equalfuncs.c | 42 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 33 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_agg.c | 1 -
src/backend/parser/parse_utilcmd.c | 366 ++++++-
src/backend/utils/cache/relcache.c | 105 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 58 +
src/include/catalog/pg_class.h | 22 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 42 +-
src/include/parser/kwlist.h | 3 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 219 ++++
src/test/regress/expected/create_table.out | 188 +++
src/test/regress/sql/alter_table.sql | 192 +++
src/test/regress/sql/create_table.sql | 139 +++
32 files changed, 4396 insertions(+), 162 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..a4fff4f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,52 @@ 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 table (partitioned or otherwise) as
+ partition of the target table. Partition bound specification must
+ correspond with the partition method and the key of the target table.
+ The table being attached must have all the columns of the target table
+ with matching types and no more. Also, it must have all the matching
+ constraints as the target table. That includes both <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
+ constraint of the table being attached is marked <literal>NO INHERIT</literal>,
+ the command will fail; such constraints must either be dropped or
+ recreated without the <literal>NO INHERIT</literal> mark.
+ Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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 target table.
+ </para>
+ <para>
+ Note that if a partition being detached is itself a partitioned table,
+ it continues to exist as such.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -722,7 +775,9 @@ 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 the table as a
+ inheritance child of a parent table and attaching a table as partition to
+ the table.
To alter the owner, you must also be a direct or indirect member of the new
owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner
@@ -938,6 +993,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>
@@ -978,6 +1051,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.
@@ -1043,10 +1122,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
A recursive <literal>DROP COLUMN</literal> operation will remove a
descendant table's column only if the descendant does not inherit
that column from any other parents and never had an independent
- definition of the column. A nonrecursive <literal>DROP
+ definition of the column (which always holds if the descendant table
+ is a partition). A nonrecursive <literal>DROP
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
- instead marks them as independently defined rather than inherited.
+ instead marks them as independently defined rather than inherited,
+ unless the descendant table is a partition.
</para>
<para>
@@ -1054,7 +1135,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
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</> which are unsupported if
+ the table is a partitioned table.
</para>
<para>
@@ -1233,6 +1315,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..007782c 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <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 893c899..dc07d32 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <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>
@@ -1424,7 +1494,38 @@ CREATE TABLE measurement (
CREATE TABLE cities (
name text not null,
population int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (lower(name));
+</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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2994cd0..5a6c742 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -91,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -771,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -809,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -819,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -925,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1034,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1269,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2479,7 +2498,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..047249f
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1760 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; maps
+ * element-by-element with the values array */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+ int nvalues;
+ Datum *values;
+ bool has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+ Datum value;
+ int index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+ Oid oid;
+ PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n,
+ int *with);
+
+/* Support get_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+ PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+ bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ ListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ RangePartition **range_parts;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ oids[i++] = partrelid;
+ }
+
+ /* Convert from node to a internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionList **list;
+ int j;
+
+ list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+ i = 0;
+ all_values_count = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundList *list_spec;
+
+ Assert(IsA(lfirst(cell), PartitionBoundList));
+ list_spec = (PartitionBoundList *) lfirst(cell);
+
+ list[i] = make_list_from_spec(key, list_spec);
+
+ if (list[i]->has_null)
+ {
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+ all_values_count += list[i]->nvalues;
+ i++;
+ }
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (ListValue **)
+ palloc0(all_values_count * sizeof(ListValue *));
+ j = 0;
+ for (i = 0; i < nparts; i++)
+ {
+ int k;
+
+ for (k = 0; k < list[i]->nvalues; k++)
+ {
+ ListValue *list_value;
+
+ list_value = (ListValue *) palloc0(sizeof(ListValue));
+ list_value->value = datumCopy(list[i]->values[k],
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ list_value->index = i;
+ all_values[j++] = list_value;
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ range_parts = (RangePartition **)
+ palloc0(nparts * sizeof(RangePartition *));
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundRange *range_spec;
+ RangePartition *range_part;
+
+ Assert(IsA(lfirst(cell), PartitionBoundRange));
+ range_spec = (PartitionBoundRange *) lfirst(cell);
+ range_part = (RangePartition *)
+ palloc0(nparts * sizeof(RangePartition));
+ range_part->oid = oids[i];
+ range_part->range = make_range_from_spec(key, range_spec);
+ range_parts[i++] = range_part;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->bounds = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListInfo *listinfo;
+ int *mapping;
+ int next_index = 0;
+
+ listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+ mapping = (int *) palloc(sizeof(int) * nparts);
+
+ /* Initialize with invalid mapping values */
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ /* Sort so that we can perform binary search over values */
+ qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+ list_value_cmp, (void *) key);
+
+ listinfo->nvalues = all_values_count;
+ listinfo->has_null = found_null_partition;
+ listinfo->values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo->indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they are same for any two partitioning schemes
+ * with the same lists. One way to achieve this is to assign
+ * indexes in the same order as the least values of the
+ * individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo->values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ listinfo->indexes[i] = all_values[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo->indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo->null_index = mapping[null_partition_index];
+ else
+ listinfo->null_index = -1;
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of list
+ * values (viz. ordering of list values).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+
+ result->bounds->listinfo = listinfo;
+ pfree(mapping);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ RangeInfo *rangeinfo;
+
+ rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+ rangeinfo->ranges = (PartitionRange **)
+ palloc0(nparts * sizeof(PartitionRange *));
+
+ /*
+ * Sort so that we can perform binary search over ranges.
+ * Note that this will also sort oids together.
+ */
+ qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+ range_partition_cmp, (void *) key);
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = range_parts[i]->oid;
+ rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+ key);
+ }
+
+ result->bounds->rangeinfo = rangeinfo;
+ break;
+ }
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2, int n)
+{
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent. Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ Relation parent = heap_open(parentId, NoLock); /* already locked */
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionBoundList *list;
+
+ Assert(IsA(bound, PartitionBoundList));
+ list = (PartitionBoundList *) bound;
+
+ if (pdesc->nparts > 0)
+ {
+ ListInfo *listinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->listinfo);
+ listinfo = pdesc->bounds->listinfo;
+ if (list_overlaps_existing_partition(key, list, listinfo,
+ &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, list->location)));
+ }
+ }
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionBoundRange *range;
+
+ Assert(IsA(bound, PartitionBoundRange));
+ range = (PartitionBoundRange *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ RangeInfo *rangeinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+ rangeinfo = pdesc->bounds->rangeinfo;
+ if (range_overlaps_existing_partition(key, range, rangeinfo,
+ pdesc->nparts, &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, range->location)));
+ }
+ }
+ break;
+ }
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ int i;
+ ListCell *partexprs_item;
+
+ Assert(key);
+
+ if (IsA(bound, PartitionBoundList))
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+ Assert(key->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, list_spec);
+ }
+ else if (IsA(bound, PartitionBoundRange))
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+ Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, range_spec);
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ /* Find all attributes referenced and translate each reference */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+ return list_values_cmp((PartitionKey) arg,
+ (*(const ListValue **) a)->value,
+ (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+ return partition_range_cmp((PartitionKey) arg,
+ (*(const RangePartition **) a)->range,
+ (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with)
+{
+ int i;
+ PartitionList *new_list;
+
+ Assert(listinfo);
+ Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+ new_list = make_list_from_spec(key, list_spec);
+
+ if (new_list->has_null && listinfo->has_null)
+ {
+ *with = listinfo->null_index;
+ return true;
+ }
+
+ for (i = 0; i < new_list->nvalues; i++)
+ {
+ int found;
+
+ /* bsearch a new list's value in listinfo->values */
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ new_list->values[i],
+ key);
+ if (found >= 0)
+ {
+ *with = listinfo->indexes[found];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+ PartitionRangeBound *lower,
+ *upper;
+
+ range = make_range_from_spec(key, range_spec);
+ lower = range->lower;
+ upper = range->upper;
+
+ /*
+ * Range is not empty if one (and only one) of the bounds is infinity.
+ * Both cannot be infinity because of how the syntax is specified.
+ */
+ Assert(!lower->infinite || !upper->infinite);
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper < lower, then it's outright empty. Also if lower = upper
+ * and either is exclusive.
+ */
+ if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+ (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n, int *with)
+{
+ int i;
+ PartitionRange *range;
+
+ /* Create internal representation of range from range_spec */
+ range = make_range_from_spec(key, range_spec);
+
+ Assert(rangeinfo);
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+ {
+ *with = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+ partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+ return true;
+
+ if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+ partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * translate_var_attno
+ *
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand). If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, leave out the test but remove null Const from the list
+ * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The
+ * latter because null-valued expressions does not have the desired
+ * behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(list_spec->values); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ list_spec->values = list_delete_cell(list_spec->values,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = list_spec->values;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ return nulltest1 ? list_make2(nulltest1, opexpr)
+ : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+ list_make2(nulltest2, opexpr),
+ -1))
+ : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ Oid operoid;
+ uint16 strategy;
+ bool need_relabel;
+
+ /*
+ * Handle the case where the partition is bounded on only one side.
+ *
+ * In this case, consider only the first column of the key since
+ * comparison with only the first column would have determined whether
+ * whether a row went into such partition. In other words, it always
+ * follows that -INF < someval and someval < +INF.
+ */
+ if (spec->lower == NIL || spec->upper == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ bool islower,
+ inclusive;
+ NullTest *nulltest;
+
+ if (spec->lower != NIL)
+ {
+ values = spec->lower;
+ islower = true;
+ inclusive = spec->lowerinc;
+ }
+ else
+ {
+ values = spec->upper;
+ islower = false;
+ inclusive = spec->upperinc;
+ }
+
+ /* Left operand */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /* Right operand */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ /* Get the correct btree operator for given strategy */
+ operoid = get_partition_operator(key, 0, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+
+ /* Build the opexpr and return the list containing it and nulltest */
+ return list_make2(nulltest,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->partcollation[0]));
+ }
+
+ /*
+ * We must consider both the lower and upper bounds. Iterate over
+ * columns of the key.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lower, cell2, spec->upper)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Is lower_val = upper_val?
+ */
+
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /*
+ * Yes, build leftop eq lower_val
+ */
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* No need to constrain further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ *
+ * Use either the column type as the operator datatype or opclass's declared
+ * input type.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ if (need_relabel)
+ *need_relabel = false;
+
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /* Turn that bound into a list of equivalent check quals */
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+ PartitionList *list;
+ ListCell *cell;
+ int i,
+ num_non_null;
+
+
+ list = (PartitionList *) palloc0(sizeof(PartitionList));
+ list->has_null = false;
+
+ /* Never put a null into the values array, flag instead */
+ num_non_null = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->has_null = true;
+ else
+ num_non_null++;
+ }
+
+ list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+ i = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (!val->constisnull)
+ list->values[i++] = datumCopy(val->constvalue,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ }
+
+ list->nvalues = num_non_null;
+
+ return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+
+ range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ range->lower = make_range_bound(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ range->upper = make_range_bound(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+
+ return range;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->infinite = (val == NIL);
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->val[i] = datumCopy(val->constvalue,
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ i++;
+ }
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+ PartitionRange *result;
+
+ result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ result->lower = copy_range_bound(src->lower, key);
+ result->upper = copy_range_bound(src->upper, key);
+
+ return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+ int i;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->lower,
+ r2->ranges[i]->lower) != 0)
+ return false;
+
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->upper,
+ r2->ranges[i]->upper) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ int cmp;
+
+ cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+ if (cmp == 0)
+ cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+ return cmp;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Compare two composite keys
+ */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ val1[i], val2[i]));
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 237d0a2..329d0b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -163,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -482,6 +491,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -594,10 +604,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -672,6 +688,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -1003,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
* but RemoveRelations() can only pass one relkind for a given relation.
@@ -1093,7 +1135,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1471,7 +1514,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1516,8 +1560,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1544,6 +1588,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1579,18 +1635,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1803,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1856,6 +1932,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1885,16 +1964,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1931,8 +2014,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined (unless partition) */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2222,6 +2306,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2452,7 +2541,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2600,11 +2689,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2846,8 +2936,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3126,6 +3219,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3443,6 +3541,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3513,7 +3617,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3762,6 +3872,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3947,7 +4063,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4027,6 +4143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4091,6 +4208,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4406,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4609,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4799,6 +4932,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5321,6 +5459,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5846,6 +6004,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5938,9 +6101,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6329,8 +6494,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7892,7 +8059,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
if (!is_no_inherit_constraint)
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
@@ -7951,8 +8120,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8024,6 +8195,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10191,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10203,12 +10384,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10253,37 +10429,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10318,6 +10468,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10332,16 +10545,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10392,7 +10597,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10410,12 +10615,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10437,14 +10646,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10455,8 +10668,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10471,7 +10686,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10485,7 +10702,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10504,10 +10721,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10554,7 +10775,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10562,7 +10785,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10573,6 +10798,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10586,7 +10812,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10597,6 +10825,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10610,13 +10878,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10625,19 +10891,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10647,7 +10904,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10668,11 +10925,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10681,7 +10947,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10743,7 +11009,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10774,7 +11040,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10786,30 +11052,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
+ drop_parent_dependency(RelationGetRelid(child_rel),
RelationRelationId,
RelationGetRelid(parent_rel));
-
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * ALTER TABLE <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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..1dbfd78 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+ PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+ PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5122,6 +5161,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundList:
+ retval = _copyPartitionBoundList(from);
+ break;
+ case T_PartitionBoundRange:
+ retval = _copyPartitionBoundRange(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a6421d2..03e9e60 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3416,6 +3449,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundList:
+ retval = _equalPartitionBoundList(a, b);
+ break;
+ case T_PartitionBoundRange:
+ retval = _equalPartitionBoundRange(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 417e20a..3a507ca 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundList:
+ _outPartitionBoundList(str, obj);
+ break;
+ case T_PartitionBoundRange:
+ _outPartitionBoundRange(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+ READ_LOCALS(PartitionBoundList);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+ READ_LOCALS(PartitionBoundRange);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2526,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionBoundList();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionBoundRange();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..d40d71c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%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.
@@ -572,7 +580,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
@@ -588,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -602,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2374,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <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:
@@ -2469,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionBoundList *n = makeNode(PartitionBoundList);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2886,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2931,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2953,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2964,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2973,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2994,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -4551,6 +4707,48 @@ CreateForeignTableStmt:
n->options = $14;
$$ = (Node *) n;
}
+ | CREATE FOREIGN TABLE qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $4;
+ n->base.inhRelations = list_make1($7);
+ n->base.tableElts = $8;
+ n->base.partbound = (Node *) $9;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = false;
+ /* FDW-specific data */
+ n->servername = $11;
+ n->options = $12;
+ $$ = (Node *) n;
+ }
+ | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $7->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $7;
+ n->base.inhRelations = list_make1($10);
+ n->base.tableElts = $11;
+ n->base.partbound = (Node *) $12;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = true;
+ /* FDW-specific data */
+ n->servername = $14;
+ n->options = $15;
+ $$ = (Node *) n;
+ }
;
/*****************************************************************************
@@ -11156,6 +11354,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13755,6 +13954,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13801,6 +14001,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13843,6 +14044,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
-
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..f565a5d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +92,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partspec)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -359,6 +372,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -898,6 +912,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1117,6 +1132,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3059,334 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+ PartitionBoundList *list, *result_list;
+ PartitionBoundRange *range, *result_range;
+
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (!IsA(bound, PartitionBoundList))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionBoundList *) bound;
+ result_list = makeNode(PartitionBoundList);
+
+ foreach(cell, list->values)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ char *colname;
+
+ if (!IsA(bound, PartitionBoundRange))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionBoundRange *) bound;
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionBoundRange);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower && list_length(range->lower) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (range->lower && list_length(range->lower) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ if (range->upper && list_length(range->upper) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (range->upper && list_length(range->upper) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ if (range->lower)
+ {
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ colname = deparse_expression((Node *) list_nth(partexprs, i),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ colname = deparse_expression((Node *) list_nth(partexprs, i),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e80ff80..1666233 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->bounds != NULL)
+ {
+ if (pdesc2->bounds == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+ pdesc1->nparts))
+ return false;
+ }
+ else if (pdesc2->bounds != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2609,13 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ {
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+ SWAPFIELD(MemoryContext, rd_pdcxt);
+ }
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..c3ad626 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..07a8d76
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection bounds; /* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundList,
+ T_PartitionBoundRange,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ada75bd..15e8d3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -735,6 +736,40 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1562,7 +1597,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1788,7 +1825,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR: source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 36f487a..e0181c4 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -437,3 +437,191 @@ Table "public.describe_list_key"
Partition key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+ERROR: "part_a" is a partition of "parted"
+HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 5a0d933..7255690 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,142 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- check for multi-column range partition key where tuple comparison occurs
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, b);
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
--
1.7.1
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-8.patch (20.6K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-8.patch)
download | inline diff:
From 1061da9b2d615560f26cfbebd89cd621b7738ab8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 85 +++++++++++++++++++++----
src/test/regress/expected/create_table.out | 39 +++++++++++
src/test/regress/sql/create_table.sql | 12 ++++
7 files changed, 393 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 82f03ea..6e2bdde 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8411,6 +8411,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundList:
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) 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_PartitionBoundRange:
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) 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 8aa615b..9ec4323 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6243,6 +6243,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 inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15423,6 +15480,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 */
@@ -15451,8 +15519,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;
@@ -15481,7 +15552,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");
}
@@ -15546,15 +15618,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 (");
@@ -15724,6 +15803,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 e57d78e..cae1b45 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e0181c4..19257d6 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -595,6 +595,45 @@ 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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- 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 7255690..fecae9b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -551,6 +551,18 @@ 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partition cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-8.patch (16.2K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-8.patch)
download | inline diff:
From 1e84b8e7f1f663d564187947fdcfe4232396f430 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.
If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
src/backend/optimizer/plan/initsplan.c | 17 ++-
src/backend/optimizer/prep/prepunion.c | 282 +++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
3 files changed, 224 insertions(+), 84 deletions(-)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *rte = root->simple_rte_array[rti];
- if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+ if (brel == NULL)
+ continue;
+
+ /*
+ * If an "other rel" RTE is a "partitioned table", we must propagate
+ * the lateral info inherited all the way from the root parent to its
+ * children. That's because the children are not linked directly with
+ * the root parent via AppendRelInfo's unlike in case of a regular
+ * inheritance set (see expand_inherited_rtentry()). Failing to
+ * do this would result in those children not getting marked with the
+ * appropriate lateral info.
+ */
+ if (brel->reloptkind != RELOPT_BASEREL &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
continue;
if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /*
+ * Do not flatten the inheritance hierarchy if partitioned table, unless
+ * this is the result relation.
+ */
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+ rti != root->parse->resultRelation)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
- */
- if (childOID != parentOID)
- {
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
- }
-
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (oldrc)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-8.patch (30.2K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-8.patch)
download | inline diff:
From b376378f7cbb06c0b8c48fbef28788861c19c045 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 5790edc..5b0e8cf 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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-8.patch (8.1K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-8.patch)
download | inline diff:
From 53d6b94dc11abcc1436538baacbc6c74dae66cdf Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 209 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 214 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 047249f..e742007 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
PartitionRange *range;
} RangePartition;
+/*
+ * 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_parts 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 RelationBuildPartitionDesc() */
static int32 list_value_cmp(const void *a, const void *b, void *arg);
static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
/* Support RelationGetPartitionQual() */
static List *generate_partition_qual(Relation rel, bool recurse);
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -754,6 +813,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1411,6 +1517,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 07a8d76..6ed8371 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -55,4 +56,8 @@ extern Oid get_partition_parent(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-8.patch (43.6K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-8.patch)
download | inline diff:
From e05d4ef20413ba6c5b19cf84701d04828b2283f9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 343 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 ++++-
src/backend/executor/nodeModifyTable.c | 142 +++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 71 +++++++
src/backend/optimizer/util/plancat.c | 20 ++-
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 59 ++++++-
src/test/regress/sql/insert.sql | 28 +++
18 files changed, 942 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e742007..8f4dd54 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
PartitionRangeBound *b2);
static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
/*
* RelationBuildPartitionDesc
@@ -1536,7 +1551,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;
@@ -1620,6 +1635,267 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ ListInfo *listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->bounds->listinfo != NULL);
+ listinfo = pdesc->bounds->listinfo;
+
+ if (isnull && listinfo->has_null)
+ return listinfo->null_index;
+ else if (!isnull)
+ {
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ value,
+ key);
+ if (found >= 0)
+ return listinfo->indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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);
+ Assert(pdesc->bounds->rangeinfo != NULL);
+
+ return bsearch_ranges(key, pdesc->nparts,
+ pdesc->bounds->rangeinfo, tuple);
+}
+
/* List partition related support functions */
/*
@@ -1967,3 +2243,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
return result;
}
+
+/*
+ * bsearch_ranges
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = n - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (rangeinfo->ranges[idx]->upper->infinite)
+ {
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+ return idx;
+
+ break;
+ }
+ else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+ {
+ if (rangeinfo->ranges[idx]->lower->infinite)
+ return idx;
+
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->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, PartitionRangeBound *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, PartitionRangeBound *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 44c273c..8c7e5f2 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.
@@ -1421,6 +1427,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1716,6 +1810,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1729,6 +1825,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);
}
@@ -2279,6 +2392,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2299,7 +2413,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2407,6 +2522,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2434,6 +2550,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;
@@ -2455,10 +2572,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 */
@@ -2510,6 +2663,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 */
@@ -2530,7 +2708,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)
@@ -2560,7 +2747,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),
@@ -2580,6 +2768,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2593,7 +2787,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 329d0b4..3e9b171 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1256,6 +1256,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 5b0e8cf..5e3922f 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,98 @@ 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_TABLE)
+ {
+ 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)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ 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 +2082,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1dbfd78..248569d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3a507ca..7dd6e10 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e11e670..bc5ad4c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..476c40f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6152,6 +6152,77 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 6ed8371..08c38b2 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/rel.h"
@@ -60,4 +62,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 697c90f..6e51773 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"
@@ -1141,6 +1142,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-8.patch (24.7K, ../../8e05817e-14c8-13e4-502b-e440adb24258@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-8.patch)
download | inline diff:
From 22b9ce03f6ae31c178b2d48ab97cedadf106d8e4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 f43352c..8084029 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-07 08:33 Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Rajkumar Raghuwanshi @ 2016-10-07 08:33 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Attached revised patches. Also, includes a fix for an issue reported by
> Rajkumar Raghuwanshi [1] which turned out to be a bug in one of the later
> patches. I will now move on to addressing the comments on patch 0003.
>
> Thanks a lot for the review!
>
> Thanks,
> Amit
>
> [1]
> https://www.postgresql.org/message-id/5dded2f1-c7f6-e7fc-56b
> 5-23ab59495e4b@lab.ntt.co.jp
Hi,
I have applied latest patches, getting some error and crash, please check
if you are also able to reproduce the same.
Observation1 : Not able to create index on partition table.
--------------
CREATE TABLE rp (c1 int, c2 int) PARTITION BY RANGE(c1);
CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES START (1) END (10);
CREATE TABLE rp_p2 PARTITION OF rp FOR VALUES START (10) END (20);
CREATE INDEX idx_rp_c1 on rp(c1);
ERROR: cannot create index on partitioned table "rp"
Observation2 : Getting cache lookup failed error for multiple column range
partition
--------------
CREATE TABLE rp1_m (c1 int, c2 int) PARTITION BY RANGE(c1, ((c1 + c2)/2));
CREATE TABLE rp1_m_p1 PARTITION OF rp1_m FOR VALUES START (1, 1) END (10,
10);
ERROR: cache lookup failed for attribute 0 of relation 16429
Observation3 : Getting server crash with multiple column range partition
--------------
CREATE TABLE rp2_m (c1 int, c2 int) PARTITION BY RANGE(((c2 + c1)/2), c2);
CREATE TABLE rp2_m_p1 PARTITION OF rp2_m FOR VALUES START (1, 1) END (10,
10);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>
Thanks & Regards,
Rajkumar Raghuwanshi
QMG, EnterpriseDB Corporation
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-07 09:20 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-10-07 09:20 UTC (permalink / raw)
To: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/07 17:33, Rajkumar Raghuwanshi wrote:
> On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote wrote:
> I have applied latest patches, getting some error and crash, please check
> if you are also able to reproduce the same.
>
> Observation1 : Not able to create index on partition table.
> --------------
> CREATE TABLE rp (c1 int, c2 int) PARTITION BY RANGE(c1);
> CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES START (1) END (10);
> CREATE TABLE rp_p2 PARTITION OF rp FOR VALUES START (10) END (20);
>
> CREATE INDEX idx_rp_c1 on rp(c1);
> ERROR: cannot create index on partitioned table "rp"
This one is a new behavior as I mentioned earlier in my previous email.
> Observation2 : Getting cache lookup failed error for multiple column range
> partition
> --------------
> CREATE TABLE rp1_m (c1 int, c2 int) PARTITION BY RANGE(c1, ((c1 + c2)/2));
>
> CREATE TABLE rp1_m_p1 PARTITION OF rp1_m FOR VALUES START (1, 1) END (10,
> 10);
> ERROR: cache lookup failed for attribute 0 of relation 16429
>
> Observation3 : Getting server crash with multiple column range partition
> --------------
> CREATE TABLE rp2_m (c1 int, c2 int) PARTITION BY RANGE(((c2 + c1)/2), c2);
> CREATE TABLE rp2_m_p1 PARTITION OF rp2_m FOR VALUES START (1, 1) END (10,
> 10);
> server closed the connection unexpectedly
> This probably means the server terminated abnormally
> before or while processing the request.
> The connection to the server was lost. Attempting reset: Failed.
> !>
Fixed. Should have been caught when running the regression tests after I
made changes to 0001 to address some review comments (apparently there was
no test in 0003 that would've caught this, so added a new one, thanks!).
Attached updated patches.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-9.patch (113.3K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-9.patch)
download | inline diff:
From c7390f79a3cf11a6ca2d580b081ea87856954045 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 160 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 9 +-
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 | 434 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 +++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 257 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 5 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 36 +++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 66 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 137 +++++++++
51 files changed, 1915 insertions(+), 49 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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>partcollation</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 collation to use for partitioning.
+ </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>partattrs</>. 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..893c899 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,41 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partitioning key</firstterm>
+ for the table. When using range partitioning, the partitioning key can
+ include multiple columns or expressions, but for list partitioning, the
+ partitioning key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is routed
+ to a partition based on the value of columns or expressions in the
+ partition key. If no existing partition matches the values in the new
+ row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dbd6094..2994cd0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1101,9 +1103,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1137,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1182,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1358,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1806,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2032,6 +2044,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -2983,3 +3006,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * 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,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition key, opclass info into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references which will count as self-dependency items; in this case,
+ * the depender is the table itself (there is no such thing as partition
+ * key object).
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 457c9bb..9801f0f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9f90e62 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1256,7 +1259,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..230a7ad 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d312762..237d0a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process and store partition key information, if any */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * 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 *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (is_expr)
+ *is_expr = false;
+ if (attnum == partattno)
+ return true;
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ if (is_expr)
+ *is_expr = true;
+
+ /* Find all attributes referenced */
+ 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;
+
+ if (attno == attnum)
+ return true;
+ }
+ partexprs_item = lnext(partexprs_item);
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5840,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 +5885,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 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7862,6 +8017,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7892,6 +8048,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7907,7 +8076,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8934,6 +9104,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9396,6 +9567,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9818,7 +9990,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10017,6 +10190,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10068,6 +10246,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11446,6 +11631,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11895,7 +12081,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12049,6 +12235,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12060,3 +12247,236 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partition strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears 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_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..133776d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1112,6 +1120,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1227,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..5790edc 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 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_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +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_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e80ff80 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..4a50cb8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..e4d7f4e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;
/*
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..95959c0
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..ada75bd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ 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 *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name ('list', 'range', etc.)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ List *partexprs; /* partition key expressions, if any */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation */
+ Oid *partcollation;
+
+ /* Type information of partition attributes */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 3232cda..140026c 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2914,3 +2914,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..5f31540 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ 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 list partition using more than one column
+-- 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: set-returning functions are not allowed in partition key expression
+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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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
+-- 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 a btree operator class or define a default btree operator class for the data 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 a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..022a239 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 72e65d4..49fbab6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..48a660f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,140 @@ 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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+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();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE fail_immut_func_key (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-9.patch (26.2K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-9.patch)
download | inline diff:
From f5a8f2425e637d010b83dd68d50611228747a749 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 146 ++++++++++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 134 ++++++++++++++++++++++++-
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, 366 insertions(+), 24 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..82f03ea 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_table.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,150 @@ 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 relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ 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);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ /*
+ * 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 299e887..8aa615b 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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,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");
@@ -5235,7 +5240,108 @@ getTables(Archive *fout, int *numTables)
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
- if (fout->remoteVersion >= 90600)
+ if (fout->remoteVersion >= 100000)
+ {
+ PQExpBuffer acl_subquery = createPQExpBuffer();
+ PQExpBuffer racl_subquery = createPQExpBuffer();
+ PQExpBuffer initacl_subquery = createPQExpBuffer();
+ PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+ PQExpBuffer attacl_subquery = createPQExpBuffer();
+ PQExpBuffer attracl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ *
+ * Left join to detect if any privileges are still as-set-at-init, in
+ * which case we won't dump out ACL commands for those.
+ */
+
+ buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+ initracl_subquery, "c.relacl", "c.relowner",
+ "CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::\"char\"",
+ dopt->binary_upgrade);
+
+ buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+ attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+ dopt->binary_upgrade);
+
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "%s AS relacl, %s as rrelacl, "
+ "%s AS initrelacl, %s as initrrelacl, "
+ "c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relrowsecurity, c.relforcerowsecurity, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "c.relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "tc.reloptions AS toast_reloptions, "
+ "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = at.attnum)"
+ "WHERE at.attrelid = c.oid AND ("
+ "%s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL"
+ "))"
+ "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 "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = 0) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ acl_subquery->data,
+ racl_subquery->data,
+ initacl_subquery->data,
+ initracl_subquery->data,
+ username_subquery,
+ attacl_subquery->data,
+ attracl_subquery->data,
+ attinitacl_subquery->data,
+ attinitracl_subquery->data,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_TABLE);
+
+ destroyPQExpBuffer(acl_subquery);
+ destroyPQExpBuffer(racl_subquery);
+ destroyPQExpBuffer(initacl_subquery);
+ destroyPQExpBuffer(initracl_subquery);
+
+ destroyPQExpBuffer(attacl_subquery);
+ destroyPQExpBuffer(attracl_subquery);
+ destroyPQExpBuffer(attinitacl_subquery);
+ destroyPQExpBuffer(attinitracl_subquery);
+ }
+ else if (fout->remoteVersion >= 90600)
{
PQExpBuffer acl_subquery = createPQExpBuffer();
PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5884,6 +5990,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)
{
@@ -5954,6 +6061,10 @@ 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));
+ if (i_partkeydef == -1 || PQgetisnull(res, i, i_partkeydef))
+ tblinfo[i].partkeydef = NULL;
+ else
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5998,7 +6109,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6100,7 +6213,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);
@@ -15456,6 +15572,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15516,6 +15635,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15534,7 +15654,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15751,6 +15872,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 6275a68..e57d78e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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 e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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 5f31540..36f487a 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -411,3 +411,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: 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 48a660f..5a0d933 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-9.patch (203.7K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-9.patch)
download | inline diff:
From fb29206c05b8c27763c7ee620bc11836cea56ab8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class get snew fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 101 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 26 +
doc/src/sgml/ref/create_table.sgml | 99 ++-
src/backend/bootstrap/bootparse.y | 1 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 33 +-
src/backend/catalog/index.c | 3 +-
src/backend/catalog/partition.c | 1760 ++++++++++++++++++++++++++++
src/backend/catalog/toasting.c | 1 +
src/backend/commands/cluster.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 774 ++++++++++--
src/backend/nodes/copyfuncs.c | 48 +
src/backend/nodes/equalfuncs.c | 42 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 33 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_agg.c | 1 -
src/backend/parser/parse_utilcmd.c | 373 ++++++-
src/backend/utils/cache/relcache.c | 105 ++-
src/include/catalog/heap.h | 4 +-
src/include/catalog/partition.h | 58 +
src/include/catalog/pg_class.h | 22 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 42 +-
src/include/parser/kwlist.h | 3 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 219 ++++
src/test/regress/expected/create_table.out | 188 +++
src/test/regress/sql/alter_table.sql | 192 +++
src/test/regress/sql/create_table.sql | 139 +++
32 files changed, 4387 insertions(+), 160 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..c4061c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,48 @@ 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 table (which might itself be partitioned)
+ as partition of the target table. The partition bound specification must
+ correspond to the partitioning method and partitioning key of the target
+ table. The table to be attached must have all the columns as the target
+ table and no more; moreover, the column types must also match. Also, it
+ must have all the matching constraints as the target table.
+ That includes both <literal>NOT NULL</literal> and <literal>CHECK</literal>
+ constraints. If some <literal>CHECK</literal> constraint of the table
+ being attached is marked <literal>NO INHERIT</literal>, the command will
+ fail; such constraints must either be dropped or recreated without the
+ <literal>NO INHERIT</literal> mark. Currently <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>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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, but no longer has any
+ ties to the table from which it was detached.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -721,8 +770,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>.
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.
+ To add the table as a new child of a parent table, or as a new partition
+ of a partitioned table, you must own the parent table as well.
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
@@ -938,6 +987,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 or to 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>
@@ -978,6 +1045,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 specified.
+ </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.
@@ -1047,6 +1120,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited.
+ A nonrecursive <literal>DROP TABLE</literal> command will fail for a
+ partitioned table, because all partitions of a table must have the same
+ columns as the partitioning root.
</para>
<para>
@@ -1233,6 +1309,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..f05f212 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,12 @@ 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.
+ </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 +329,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</>, as a 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 893c899..cb78145 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,14 @@ 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | 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 }
+
<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 +99,8 @@ 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>
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +251,51 @@ 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.
+ </para>
+
+ <para>
+ The partition bound specification must correspond to the partitioning
+ method and partitioning key of the parent table, and must not overlap
+ with any existing partition of that 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. One can also specify table
+ constraints, in addition to those inherited from the parent.
+ </para>
+
+ <para>
+ Rows inserted into a partitioned table will be automatically routed to
+ the correct partition. If no suitable partition exists, an error will
+ occur.
+ </para>
+
+ <para>
+ A partition must have the same column names and types as the table of
+ which it is a partition. Therefore, modifications to the column names
+ or types of the partitioned table will automatically propagate to all
+ children, as will operations such as TRUNCATE which normally affect a
+ table and all of its inheritance children. It is also possible to
+ TRUNCATE a partition individually, just as for an inheritance child.
+ 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>
@@ -1424,7 +1488,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..ecf8a75 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -248,6 +248,7 @@ Boot_CreateStmt:
0,
ONCOMMIT_NOOP,
(Datum) 0,
+ (Datum) 0,
false,
true,
false,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2994cd0..5a6c742 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -91,7 +91,8 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
static ObjectAddress AddNewRelationType(const char *typeName,
Oid typeNamespace,
Oid new_rel_oid,
@@ -771,7 +772,8 @@ InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class rd_rel = new_rel_desc->rd_rel;
Datum values[Natts_pg_class];
@@ -809,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -819,6 +822,10 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_reloptions - 1] = reloptions;
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ if (relpartbound != (Datum) 0)
+ values[Anum_pg_class_relpartbound - 1] = relpartbound;
+ else
+ nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
@@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid relowner,
char relkind,
Datum relacl,
- Datum reloptions)
+ Datum reloptions,
+ Datum relpartbound)
{
Form_pg_class new_rel_reltup;
@@ -925,11 +933,13 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relispartition = (relpartbound != (Datum) 0);
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid,
- relacl, reloptions);
+ relacl, reloptions, relpartbound);
}
@@ -1034,6 +1044,7 @@ heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -1269,7 +1280,8 @@ heap_create_with_catalog(const char *relname,
ownerid,
relkind,
PointerGetDatum(relacl),
- reloptions);
+ reloptions,
+ relpartbound);
/*
* now add tuples to pg_attribute for the attributes in our new relation.
@@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
+ if (is_no_inherit && rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("Table \"%s\" is a partition.",
+ RelationGetRelationName(rel))));
/*
* Create the Check Constraint
@@ -2479,7 +2498,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
con->conislocal = true;
else
con->coninhcount++;
- if (is_no_inherit)
+
+ /* Discard the NO INHERIT flag if the relation is a partition */
+ if (is_no_inherit && !rel->rd_rel->relispartition)
{
Assert(is_local);
con->connoinherit = true;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b0989..79714ae 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -888,7 +888,8 @@ index_create(Relation heapRelation,
InsertPgClassTuple(pg_class, indexRelation,
RelationGetRelid(indexRelation),
(Datum) 0,
- reloptions);
+ reloptions,
+ (Datum) 0);
/* done with pg_class */
heap_close(pg_class, RowExclusiveLock);
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..047249f
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1760 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical relation)
+ *
+ * Depending on whether the relation in question is list or range
+ * partitioned, one of the fields is set.
+ */
+typedef struct BoundCollectionData
+{
+ struct ListInfo *listinfo;
+ struct RangeInfo *rangeinfo;
+} BoundCollectionData;
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct ListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; maps
+ * element-by-element with the values array */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} ListInfo;
+
+/*
+ * Range bound collection - sorted array of ranges of partitions of a range
+ * partitioned table
+ */
+typedef struct RangeInfo
+{
+ struct PartitionRange **ranges;
+} RangeInfo;
+
+/* One partition's range */
+typedef struct PartitionRange
+{
+ struct PartitionRangeBound *lower;
+ struct PartitionRangeBound *upper;
+} PartitionRange;
+
+/* Either bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+
+/*
+ * Following struct definitions are only used initially when initializing
+ * the relcache.
+ */
+
+/* One list partition */
+typedef struct PartitionList
+{
+ int nvalues;
+ Datum *values;
+ bool has_null;
+} PartitionList;
+
+/* One value coming from some (index'th) list partition */
+typedef struct ListValue
+{
+ Datum value;
+ int index;
+} ListValue;
+
+/* This has oid because we want it to be ordered along with range */
+typedef struct RangePartition
+{
+ Oid oid;
+ PartitionRange *range;
+} RangePartition;
+
+/* Support RelationBuildPartitionDesc() */
+static int32 list_value_cmp(const void *a, const void *b, void *arg);
+static int32 range_partition_cmp(const void *a, const void *b, void *arg);
+
+/* Support check_new_partition_bound() */
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionBoundRange *range_spec);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n,
+ int *with);
+
+/* Support get_qual_from_partbound */
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *get_qual_for_list(PartitionKey key, PartitionBoundList *list);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *range);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+/* Support RelationGetPartitionQual() */
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static PartitionList *make_list_from_spec(PartitionKey key,
+ PartitionBoundList *list_spec);
+static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n);
+static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key);
+
+/* Range partition related support functions */
+static PartitionRange *make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec);
+static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive,
+ bool lower);
+static PartitionRange *copy_range(PartitionRange *src, PartitionKey key);
+static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key);
+static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n);
+static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
+static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partitions.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ ListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ RangePartition **range_parts;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ partoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, partrelid);
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ oids[i++] = partrelid;
+ }
+
+ /* Convert from node to a internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionList **list;
+ int j;
+
+ list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *));
+
+ i = 0;
+ all_values_count = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundList *list_spec;
+
+ Assert(IsA(lfirst(cell), PartitionBoundList));
+ list_spec = (PartitionBoundList *) lfirst(cell);
+
+ list[i] = make_list_from_spec(key, list_spec);
+
+ if (list[i]->has_null)
+ {
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+ all_values_count += list[i]->nvalues;
+ i++;
+ }
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (ListValue **)
+ palloc0(all_values_count * sizeof(ListValue *));
+ j = 0;
+ for (i = 0; i < nparts; i++)
+ {
+ int k;
+
+ for (k = 0; k < list[i]->nvalues; k++)
+ {
+ ListValue *list_value;
+
+ list_value = (ListValue *) palloc0(sizeof(ListValue));
+ list_value->value = datumCopy(list[i]->values[k],
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ list_value->index = i;
+ all_values[j++] = list_value;
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ range_parts = (RangePartition **)
+ palloc0(nparts * sizeof(RangePartition *));
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundRange *range_spec;
+ RangePartition *range_part;
+
+ Assert(IsA(lfirst(cell), PartitionBoundRange));
+ range_spec = (PartitionBoundRange *) lfirst(cell);
+ range_part = (RangePartition *)
+ palloc0(nparts * sizeof(RangePartition));
+ range_part->oid = oids[i];
+ range_part->range = make_range_from_spec(key, range_spec);
+ range_parts[i++] = range_part;
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->bounds = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListInfo *listinfo;
+ int *mapping;
+ int next_index = 0;
+
+ listinfo = (ListInfo *) palloc0(sizeof(ListInfo));
+ mapping = (int *) palloc(sizeof(int) * nparts);
+
+ /* Initialize with invalid mapping values */
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ /* Sort so that we can perform binary search over values */
+ qsort_arg(all_values, all_values_count, sizeof(ListValue *),
+ list_value_cmp, (void *) key);
+
+ listinfo->nvalues = all_values_count;
+ listinfo->has_null = found_null_partition;
+ listinfo->values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo->indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they are same for any two partitioning schemes
+ * with the same lists. One way to achieve this is to assign
+ * indexes in the same order as the least values of the
+ * individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo->values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ listinfo->indexes[i] = all_values[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo->indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo->null_index = mapping[null_partition_index];
+ else
+ listinfo->null_index = -1;
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of list
+ * values (viz. ordering of list values).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+
+ result->bounds->listinfo = listinfo;
+ pfree(mapping);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ RangeInfo *rangeinfo;
+
+ rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo));
+ rangeinfo->ranges = (PartitionRange **)
+ palloc0(nparts * sizeof(PartitionRange *));
+
+ /*
+ * Sort so that we can perform binary search over ranges.
+ * Note that this will also sort oids together.
+ */
+ qsort_arg(range_parts, nparts, sizeof(RangePartition *),
+ range_partition_cmp, (void *) key);
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = range_parts[i]->oid;
+ rangeinfo->ranges[i] = copy_range(range_parts[i]->range,
+ key);
+ }
+
+ result->bounds->rangeinfo = rangeinfo;
+ break;
+ }
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of a set partition bounds (for given partitioning strategy).
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2, int n)
+{
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key, b1->listinfo, b2->listinfo, n))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Call partition method specific routines to check if the new partition's
+ * bound overlaps any of the existing partitions of parent. Some partition
+ * types may have still other validations to perform, for example, a range
+ * partition with an empty range is not valid.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ Relation parent = heap_open(parentId, NoLock); /* already locked */
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionBoundList *list;
+
+ Assert(IsA(bound, PartitionBoundList));
+ list = (PartitionBoundList *) bound;
+
+ if (pdesc->nparts > 0)
+ {
+ ListInfo *listinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->listinfo);
+ listinfo = pdesc->bounds->listinfo;
+ if (list_overlaps_existing_partition(key, list, listinfo,
+ &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, list->location)));
+ }
+ }
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionBoundRange *range;
+
+ Assert(IsA(bound, PartitionBoundRange));
+ range = (PartitionBoundRange *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, range->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ RangeInfo *rangeinfo;
+
+ Assert(pdesc->bounds && pdesc->bounds->rangeinfo);
+ rangeinfo = pdesc->bounds->rangeinfo;
+ if (range_overlaps_existing_partition(key, range, rangeinfo,
+ pdesc->nparts, &with))
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, range->location)));
+ }
+ }
+ break;
+ }
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of relid by scanning pg_inherits
+ *
+ * Note: This function should be called only when it is known that 'relid'
+ * is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition predicate
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ int i;
+ ListCell *partexprs_item;
+
+ Assert(key);
+
+ if (IsA(bound, PartitionBoundList))
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) bound;
+
+ Assert(key->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, list_spec);
+ }
+ else if (IsA(bound, PartitionBoundRange))
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) bound;
+
+ Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, range_spec);
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ /* Simple column reference */
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ /* Find all attributes referenced and translate each reference */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_qual = (List *) translate_var_attno((Node *) my_qual,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+list_value_cmp(const void *a, const void *b, void *arg)
+{
+ return list_values_cmp((PartitionKey) arg,
+ (*(const ListValue **) a)->value,
+ (*(const ListValue **) b)->value);
+}
+
+/*
+ * range_partition_cmp
+ *
+ * Compare two range partitions
+ */
+static int32
+range_partition_cmp(const void *a, const void *b, void *arg)
+{
+ return partition_range_cmp((PartitionKey) arg,
+ (*(const RangePartition **) a)->range,
+ (*(const RangePartition **) b)->range);
+}
+
+/*
+ * list_overlaps_existing_partition
+ *
+ * Does a new list partition overlap any of existing partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundList *list_spec,
+ ListInfo *listinfo,
+ int *with)
+{
+ int i;
+ PartitionList *new_list;
+
+ Assert(listinfo);
+ Assert(listinfo->nvalues > 0 || listinfo->has_null);
+
+ new_list = make_list_from_spec(key, list_spec);
+
+ if (new_list->has_null && listinfo->has_null)
+ {
+ *with = listinfo->null_index;
+ return true;
+ }
+
+ for (i = 0; i < new_list->nvalues; i++)
+ {
+ int found;
+
+ /* bsearch a new list's value in listinfo->values */
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ new_list->values[i],
+ key);
+ if (found >= 0)
+ {
+ *with = listinfo->indexes[found];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ * Is a new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+ PartitionRangeBound *lower,
+ *upper;
+
+ range = make_range_from_spec(key, range_spec);
+ lower = range->lower;
+ upper = range->upper;
+
+ /*
+ * Range is not empty if one (and only one) of the bounds is infinity.
+ * Both cannot be infinity because of how the syntax is specified.
+ */
+ Assert(!lower->infinite || !upper->infinite);
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper < lower, then it's outright empty. Also if lower = upper
+ * and either is exclusive.
+ */
+ if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 ||
+ (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/*
+ * range_overlaps_existing_partition
+ *
+ * Does the new range partition overlap any of existing partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionBoundRange *range_spec,
+ RangeInfo *rangeinfo,
+ int n, int *with)
+{
+ int i;
+ PartitionRange *range;
+
+ /* Create internal representation of range from range_spec */
+ range = make_range_from_spec(key, range_spec);
+
+ Assert(rangeinfo);
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_overlaps(key, range, rangeinfo->ranges[i]))
+ {
+ *with = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 &&
+ partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0)
+ return true;
+
+ if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 &&
+ partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * translate_var_attno
+ *
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * get_qual_for_list
+ *
+ * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the
+ * partition key (left operand) and PartitionBoundList (right operand). If the
+ * partition does not accept nulls, also include a IS NOT NULL test.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundList *list_spec)
+{
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, leave out the test but remove null Const from the list
+ * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The
+ * latter because null-valued expressions does not have the desired
+ * behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(list_spec->values); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ list_spec->values = list_delete_cell(list_spec->values,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = list_spec->values;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ return nulltest1 ? list_make2(nulltest1, opexpr)
+ : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR,
+ list_make2(nulltest2, opexpr),
+ -1))
+ : list_make1(opexpr));
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint, given the
+ * partition key (left operands) and PartitionBoundRange (right operands)
+ *
+ * For each column, a IS NOT NULL test is emitted since we do not allow null
+ * values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundRange *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ Oid operoid;
+ uint16 strategy;
+ bool need_relabel;
+
+ /*
+ * Handle the case where the partition is bounded on only one side.
+ *
+ * In this case, consider only the first column of the key since
+ * comparison with only the first column would have determined whether
+ * whether a row went into such partition. In other words, it always
+ * follows that -INF < someval and someval < +INF.
+ */
+ if (spec->lower == NIL || spec->upper == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ bool islower,
+ inclusive;
+ NullTest *nulltest;
+
+ if (spec->lower != NIL)
+ {
+ values = spec->lower;
+ islower = true;
+ inclusive = spec->lowerinc;
+ }
+ else
+ {
+ values = spec->upper;
+ islower = false;
+ inclusive = spec->upperinc;
+ }
+
+ /* Left operand */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /* Right operand */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ /* Get the correct btree operator for given strategy */
+ operoid = get_partition_operator(key, 0, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+
+ /* Build the opexpr and return the list containing it and nulltest */
+ return list_make2(nulltest,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->partcollation[0]));
+ }
+
+ /*
+ * We must consider both the lower and upper bounds. Iterate over
+ * columns of the key.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lower, cell2, spec->upper)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Is lower_val = upper_val?
+ */
+
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /*
+ * Yes, build leftop eq lower_val
+ */
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* No need to constrain further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ *
+ * Use either the column type as the operator datatype or opclass's declared
+ * input type.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ if (need_relabel)
+ *need_relabel = false;
+
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Generate from pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ /* Turn that bound into a list of equivalent check quals */
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Make a PartitionList from a PartitionBoundList
+ */
+static PartitionList *
+make_list_from_spec(PartitionKey key, PartitionBoundList *list_spec)
+{
+ PartitionList *list;
+ ListCell *cell;
+ int i,
+ num_non_null;
+
+
+ list = (PartitionList *) palloc0(sizeof(PartitionList));
+ list->has_null = false;
+
+ /* Never put a null into the values array, flag instead */
+ num_non_null = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->has_null = true;
+ else
+ num_non_null++;
+ }
+
+ list->values = (Datum *) palloc0(num_non_null * sizeof(Datum));
+ i = 0;
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ if (!val->constisnull)
+ list->values[i++] = datumCopy(val->constvalue,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ }
+
+ list->nvalues = num_non_null;
+
+ return list;
+}
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list values */
+static int32
+list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+bsearch_list_values(const Datum *values, int n, const Datum probe,
+ PartitionKey key)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Make a PartitionRange from given PartitionBoundRange
+ */
+static PartitionRange *
+make_range_from_spec(PartitionKey key, PartitionBoundRange *range_spec)
+{
+ PartitionRange *range;
+
+ range = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ range->lower = make_range_bound(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ range->upper = make_range_bound(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+
+ return range;
+}
+
+/*
+ * Make PartitionRangeBound with given value (possibly composite) and
+ * inclusivity.
+ */
+static PartitionRangeBound *
+make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->infinite = (val == NIL);
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->val[i] = datumCopy(val->constvalue,
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ i++;
+ }
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRange.
+ */
+static PartitionRange *
+copy_range(PartitionRange *src, PartitionKey key)
+{
+ PartitionRange *result;
+
+ result = (PartitionRange *) palloc0(sizeof(PartitionRange));
+ result->lower = copy_range_bound(src->lower, key);
+ result->upper = copy_range_bound(src->upper, key);
+
+ return result;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n)
+{
+ int i;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < n; i++)
+ {
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->lower,
+ r2->ranges[i]->lower) != 0)
+ return false;
+
+ if (partition_range_bound_cmp(key,
+ r1->ranges[i]->upper,
+ r2->ranges[i]->upper) != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Compare two non-empty ranges (cf. range_cmp)
+ */
+static int32
+partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2)
+{
+ int cmp;
+
+ cmp = partition_range_bound_cmp(key, r1->lower, r2->lower);
+ if (cmp == 0)
+ cmp = partition_range_bound_cmp(key, r1->upper, r2->upper);
+
+ return cmp;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ */
+static int32
+partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = partition_range_tuple_cmp(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Compare two composite keys
+ */
+static int32
+partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ val1[i], val2[i]));
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..9482c10 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..417d3e2 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
0,
ONCOMMIT_NOOP,
reloptions,
+ (Datum) 0,
false,
true,
true,
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 237d0a2..329d0b4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -163,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -482,6 +491,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults;
List *cookedDefaults;
Datum reloptions;
+ Datum relpartbound;
ListCell *listptr;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
@@ -594,10 +604,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -672,6 +688,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ char *boundString;
+ Oid parentId = linitial_oid(inheritOids);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, stmt->partbound);
+ boundString = nodeToString(stmt->partbound);
+ relpartbound = CStringGetTextDatum(boundString);
+ }
+ else
+ relpartbound = (Datum) 0;
+
/*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
@@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
parentOidCount,
stmt->oncommit,
reloptions,
+ relpartbound,
true,
allowSystemTableMods,
false,
@@ -1003,6 +1038,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
/*
* Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
* but RemoveRelations() can only pass one relkind for a given relation.
@@ -1093,7 +1135,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1471,7 +1514,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1516,8 +1560,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1544,6 +1588,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1579,18 +1635,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1803,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1856,6 +1932,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1885,16 +1964,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1931,8 +2014,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined (unless partition) */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2222,6 +2306,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (classform->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2452,7 +2541,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2600,11 +2689,12 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2846,8 +2936,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
+ interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -3126,6 +3219,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3443,6 +3541,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3513,7 +3617,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3762,6 +3872,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3947,7 +4063,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4027,6 +4143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4091,6 +4208,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4406,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("source table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4609,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4799,6 +4932,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5321,6 +5459,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if we shouldn't be dropping the
+ * NOT NULL because it is present in the parent.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5846,6 +6004,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5938,9 +6101,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
/*
* If the child column has other definition sources, just
* decrement its inheritance count; if not, recurse to delete
- * it.
+ * it. If the child table is partition, remain in sync with
+ * the parent.
*/
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (childatt->attinhcount == 1 &&
+ (!childatt->attislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child column, too */
ATExecDropColumn(wqueue, childrel, colName,
@@ -6329,8 +6494,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * Remember that we discard is_no_inherit for partitioned tables.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return address;
/*
@@ -7892,7 +8059,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
if (!is_no_inherit_constraint)
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
@@ -7951,8 +8120,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
+ * If the child table is partition, remain in sync with the parent.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (con->coninhcount == 1 &&
+ (!con->conislocal || childrel->rd_rel->relispartition))
{
/* Time to delete this child constraint, too */
ATExecDropConstraint(childrel, constrName, behavior,
@@ -8024,6 +8195,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -10191,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10203,12 +10384,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10253,37 +10429,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10318,6 +10468,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10332,16 +10545,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10392,7 +10597,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10410,12 +10615,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10437,14 +10646,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different type for column \"%s\""
+ : "child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different collation for column \"%s\""
+ : "source table \"%s\" has different collation for column \"%s\"",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10455,8 +10668,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg(is_attach_partition
+ ? "column \"%s\" in source table must be marked NOT NULL"
+ : "column \"%s\" in child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10471,7 +10686,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing column \"%s\""
+ : "child table is missing column \"%s\"",
attributeName)));
}
}
@@ -10485,7 +10702,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10504,10 +10721,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10554,7 +10775,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table \"%s\" has different definition for check constraint \"%s\""
+ : "child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10562,7 +10785,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg(is_attach_partition
+ ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\""
+ : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
NameStr(child_con->conname),
RelationGetRelationName(child_rel))));
@@ -10573,6 +10798,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10586,7 +10812,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg(is_attach_partition
+ ? "source table is missing constraint \"%s\""
+ : "child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
}
@@ -10597,6 +10825,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10610,13 +10878,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10625,19 +10891,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10647,7 +10904,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10668,11 +10925,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10681,7 +10947,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10743,7 +11009,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10774,7 +11040,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10786,30 +11052,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
+ drop_parent_dependency(RelationGetRelid(child_rel),
RelationRelationId,
RelationGetRelid(parent_rel));
-
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * ALTER TABLE <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,
+ inheritsRel,
+ classRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..1dbfd78 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundList *
+_copyPartitionBoundList(const PartitionBoundList *from)
+{
+ PartitionBoundList *newnode = makeNode(PartitionBoundList);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionBoundRange *
+_copyPartitionBoundRange(const PartitionBoundRange *from)
+{
+ PartitionBoundRange *newnode = makeNode(PartitionBoundRange);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5122,6 +5161,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundList:
+ retval = _copyPartitionBoundList(from);
+ break;
+ case T_PartitionBoundRange:
+ retval = _copyPartitionBoundRange(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a6421d2..03e9e60 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3416,6 +3449,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundList:
+ retval = _equalPartitionBoundList(a, b);
+ break;
+ case T_PartitionBoundRange:
+ retval = _equalPartitionBoundRange(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 417e20a..3a507ca 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundList(StringInfo str, const PartitionBoundList *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundList:
+ _outPartitionBoundList(str, obj);
+ break;
+ case T_PartitionBoundRange:
+ _outPartitionBoundRange(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..e11e670 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundList
+ */
+static PartitionBoundList *
+_readPartitionBoundList(void)
+{
+ READ_LOCALS(PartitionBoundList);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionBoundRange
+ */
+static PartitionBoundRange *
+_readPartitionBoundRange(void)
+{
+ READ_LOCALS(PartitionBoundRange);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2526,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionBoundList();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionBoundRange();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..d40d71c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%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.
@@ -572,7 +580,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
@@ -588,7 +596,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -602,7 +611,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2374,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <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:
@@ -2469,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionBoundList *n = makeNode(PartitionBoundList);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionBoundRange *n = makeNode(PartitionBoundRange);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2886,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2931,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2953,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2964,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2973,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2994,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -4551,6 +4707,48 @@ CreateForeignTableStmt:
n->options = $14;
$$ = (Node *) n;
}
+ | CREATE FOREIGN TABLE qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $4;
+ n->base.inhRelations = list_make1($7);
+ n->base.tableElts = $8;
+ n->base.partbound = (Node *) $9;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = false;
+ /* FDW-specific data */
+ n->servername = $11;
+ n->options = $12;
+ $$ = (Node *) n;
+ }
+ | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $7->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $7;
+ n->base.inhRelations = list_make1($10);
+ n->base.tableElts = $11;
+ n->base.partbound = (Node *) $12;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = true;
+ /* FDW-specific data */
+ n->servername = $14;
+ n->options = $15;
+ $$ = (Node *) n;
+ }
;
/*****************************************************************************
@@ -11156,6 +11354,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13755,6 +13954,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13801,6 +14001,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13843,6 +14044,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
-
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4e14886 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +92,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partspec)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -359,6 +372,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -898,6 +912,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1117,6 +1132,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3059,341 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ ListCell *cell;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+ PartitionBoundList *list, *result_list;
+ PartitionBoundRange *range, *result_range;
+
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (!IsA(bound, PartitionBoundList))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionBoundList *) bound;
+ result_list = makeNode(PartitionBoundList);
+
+ foreach(cell, list->values)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ int i,
+ j;
+ char *colname;
+
+ if (!IsA(bound, PartitionBoundRange))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionBoundRange *) bound;
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionBoundRange);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower && list_length(range->lower) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (range->lower && list_length(range->lower) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ if (range->upper && list_length(range->upper) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (range->upper && list_length(range->upper) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ if (range->lower)
+ {
+ i = j = 0;
+ foreach (cell, range->lower)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ i = j = 0;
+ foreach (cell, range->upper)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation(value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e80ff80..1666233 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->bounds != NULL)
+ {
+ if (pdesc2->bounds == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds,
+ pdesc1->nparts))
+ return false;
+ }
+ else if (pdesc2->bounds != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2609,13 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ {
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+ SWAPFIELD(MemoryContext, rd_pdcxt);
+ }
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..c3ad626 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname,
int oidinhcount,
OnCommitAction oncommit,
Datum reloptions,
+ Datum relpartbound,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
@@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc,
Relation new_rel_desc,
Oid new_rel_oid,
Datum relacl,
- Datum reloptions);
+ Datum reloptions,
+ Datum relpartbound);
extern List *AddRelationNewConstraints(Relation rel,
List *newColDefaults,
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..07a8d76
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection bounds; /* collection of list or range bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2, int n);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..727dc6e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundList,
+ T_PartitionBoundRange,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ada75bd..15e8d3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -735,6 +736,40 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundList - a list partition bound
+ */
+typedef struct PartitionBoundList
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionBoundList;
+
+/*
+ * PartitionBoundRange - a range partition bound
+ */
+typedef struct PartitionBoundRange
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionBoundRange;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1562,7 +1597,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1788,7 +1825,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 140026c..6fe7623 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted, fail_part;
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+ERROR: invalid bound specification for a list partition
+DROP TABLE fail_part;
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table that is a inheritance child as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach a typed table as partition
+DROP TYPE mytype CASCADE;
+NOTICE: drop cascades to table fail_part
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs
+DROP TABLE fail_part;
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: table "fail_part" contains column "c" not found in parent "list_parted"
+DETAIL: Table being attached should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing column "b"
+DROP TABLE fail_part;
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different type for column "b"
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table is missing constraint "check_a"
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: source table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1"
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ t | 1
+ t | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ t | 1
+(1 row)
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+ERROR: source table contains a row violating partition bound specification
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ERROR: source table contains a row violating partition bound specification
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ERROR: source table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+ERROR: "part_2" is already a partition
+-- DETACH PARTITION
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted"
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount
+-------------
+ 0
+ 0
+(2 rows)
+
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount
+-------------
+ 0
+(1 row)
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_1 DROP COLUMN b;
+ERROR: cannot drop column from a partition
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename column of a partition
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter column type of a partition
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+ERROR: cannot inherit from partition "part_1"
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_1 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 36f487a..580a872 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -437,3 +437,191 @@ Table "public.describe_list_key"
Partition key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+ERROR: both START and END cannot be UNBOUNDED
+LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES...
+ ^
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values specified than number of columns in the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values specified than number of columns in the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+ERROR: invalid input syntax for type date: "a"
+LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (...
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+ERROR: "unparted" is not partitioned
+DROP TABLE unparted;
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+ERROR: cannot create as partition of temporary relation "temp_parted"
+DROP TABLE temp_parted;
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part"
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_bc_part" would overlap partition "ab_part"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+ERROR: partition "fail_unb_2" would overlap partition "part_unb_1"
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc"
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20"
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+ count
+-------
+ 1
+(1 row)
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+ conislocal
+------------
+ t
+(1 row)
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con"
+DETAIL: Table "fail_part_no_inh_con" is a partition.
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+ERROR: "part_a" is a partition of "parted"
+HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part_a_1_a_10
+drop cascades to table part_a_10_a_20
+drop cascades to table part_b_1_b_10
+drop cascades to table part_b_10_b_20
+drop cascades to table part_1_1
+drop cascades to table part_unb_1
+drop cascades to table part_2_10_inc
+drop cascades to table nulls_z_part
+drop cascades to table ab_part
+drop cascades to table lpart1
+drop cascades to table lpart2
+drop cascades to table lpart3
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 49fbab6..8c15ba2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check target table partitioned
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_part (like unparted);
+ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
+DROP TABLE unparted, fail_part;
+
+-- check partition bounds compatible
+CREATE TABLE list_parted (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE fail_part (LIKE list_parted);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10);
+DROP TABLE fail_part;
+
+-- check the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check the table being attached is not inheritance child of some relation
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE fail_part () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check the table being attached is not a typed table
+CREATE TYPE mytype AS (a int);
+CREATE TABLE fail_part OF mytype;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TYPE mytype CASCADE;
+
+-- check the existence (or non-existence) of oid column
+ALTER TABLE list_parted SET WITH OIDS;
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+ALTER TABLE list_parted SET WITHOUT OIDS;
+ALTER TABLE fail_part SET WITH OIDS;
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached does not have columns not in the parent
+CREATE TABLE fail_part (like list_parted, c int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached has all columns of the parent
+CREATE TABLE fail_part (a int);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the columns of the table being attached match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ a int,
+ b int
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (3);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the table being attached all constraints of the parent
+CREATE TABLE fail_part (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US"
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the constraint of table being attached matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+);
+
+-- fail to attach a partition with a NO INHERIT constraint
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check the new partition does not overlap with existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check the new partition does not contain values outside specified bound
+INSERT INTO fail_part VALUES (3, 'a');
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null);
+
+DELETE FROM fail_part;
+INSERT INTO fail_part VALUES (null, 'a');
+-- fail too because null is not specified in the accepted values
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2);
+
+-- the check will be skipped, if NO VALIDATE is specified
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+
+-- same check as above but now the table being attached is itself partitioned
+CREATE TABLE part_3 (
+ a int,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+
+-- fail
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check the partition being detached is a partition (of the parent)
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that attinhcount and coninhcount dropped to 0 after detached
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Miscellaneous ALTER TABLE special behaviors for partitions
+
+-- cannot add/drop a column to/from a partition or rename it
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+ALTER TABLE part_1 RENAME COLUMN b to c;
+
+-- cannot alter type of a column of a partition
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot let a partition participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_1);
+CREATE TABLE inh_test (LIKE part_1);
+ALTER TABLE inh_test INHERIT part_1;
+ALTER TABLE part_1 INHERIT inh_test;
+
+-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+
+-- cannot drop or alter type of partition key columns of lower levels
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 5a0d933..2610222 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -419,3 +419,142 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be specified
+-- for a partition bound value
+CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a');
+-- both start and end bounds of a range partition cannot be UNBOUNDED
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED;
+-- each of start and end bounds must have same number of values as there
+-- are columns in the partition key
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b');
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a');
+DROP TABLE unparted;
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE temp_parted (
+ a int
+) PARTITION BY LIST (a);
+CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a');
+DROP TABLE temp_parted;
+
+-- cannot create a table with oids as partition of table without oids
+CREATE TABLE no_oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITHOUT OIDS;
+CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1);
+CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2);
+CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15);
+CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a varchar,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10);
+CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20);
+CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25);
+CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10);
+CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20);
+CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 1,
+ CONSTRAINT check_b CHECK (b > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- the above command creates inheritance
+SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass;
+
+-- specify a column option overriding parent's and a table constraint that will be merged
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS DEFAULT 10,
+ CONSTRAINT check_b CHECK (b > 0)
+) FOR VALUES IN ('b');
+SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b';
+
+-- cannot add NO INHERIT constraint to a partition
+CREATE TABLE fail_part_no_inh_con PARTITION OF parted (
+ CONSTRAINT chk_b CHECK (b > 0) NO INHERIT
+) FOR VALUES IN (null);
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a partition of partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partition cannot be dropped directly
+DROP TABLE part_a;
+
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
--
1.7.1
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-9.patch (20.7K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-9.patch)
download | inline diff:
From e12dd4bfd410c9c4d34bfe18428a0064783bbdf9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 105 ++++++++++++++++++++++++++--
src/bin/pg_dump/pg_dump.h | 12 +++
src/bin/psql/describe.c | 85 +++++++++++++++++++---
src/test/regress/expected/create_table.out | 39 ++++++++++
src/test/regress/sql/create_table.sql | 12 +++
7 files changed, 400 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 82f03ea..6e2bdde 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8411,6 +8411,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundList:
+ {
+ PartitionBoundList *list_spec = (PartitionBoundList *) 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_PartitionBoundRange:
+ {
+ PartitionBoundRange *range_spec = (PartitionBoundRange *) 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 8aa615b..8ece47a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6243,6 +6243,70 @@ 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;
+
+ /* Before version 10, there are no partitions */
+ if (fout->remoteVersion < 100000)
+ {
+ *numPartitions = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -15423,6 +15487,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 */
@@ -15451,8 +15526,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;
@@ -15481,7 +15559,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");
}
@@ -15546,15 +15625,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 (");
@@ -15724,6 +15810,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 e57d78e..cae1b45 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 580a872..0578389 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -595,6 +595,45 @@ 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
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition key: LIST (a)
+Check constraints:
+ "check_b" CHECK (b > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- 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 2610222..3a5301e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -551,6 +551,18 @@ 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partition cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-9.patch (16.2K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-9.patch)
download | inline diff:
From dbf31707e52176b62af400611cf725d2e9a40053 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.
If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
src/backend/optimizer/plan/initsplan.c | 17 ++-
src/backend/optimizer/prep/prepunion.c | 282 +++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
3 files changed, 224 insertions(+), 84 deletions(-)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *rte = root->simple_rte_array[rti];
- if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+ if (brel == NULL)
+ continue;
+
+ /*
+ * If an "other rel" RTE is a "partitioned table", we must propagate
+ * the lateral info inherited all the way from the root parent to its
+ * children. That's because the children are not linked directly with
+ * the root parent via AppendRelInfo's unlike in case of a regular
+ * inheritance set (see expand_inherited_rtentry()). Failing to
+ * do this would result in those children not getting marked with the
+ * appropriate lateral info.
+ */
+ if (brel->reloptkind != RELOPT_BASEREL &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
continue;
if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /*
+ * Do not flatten the inheritance hierarchy if partitioned table, unless
+ * this is the result relation.
+ */
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+ rti != root->parse->resultRelation)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
- */
- if (childOID != parentOID)
- {
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
- }
-
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (oldrc)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-9.patch (30.2K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-9.patch)
download | inline diff:
From 5906075d3fb34b94da3acced9193114849584b76 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9801f0f..44c273c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2527,7 +2527,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 5790edc..5b0e8cf 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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 4fa3661..697c90f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index d8b5b1d..3a83974 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1489,3 +1489,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index b307a50..c249b80 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -494,3 +494,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end unbounded partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-9.patch (8.1K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-9.patch)
download | inline diff:
From 27e79bc1e907ef78d38bafc92d3379be050b7447 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 209 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 214 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 047249f..e742007 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,61 @@ typedef struct RangePartition
PartitionRange *range;
} RangePartition;
+/*
+ * 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_parts 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 RelationBuildPartitionDesc() */
static int32 list_value_cmp(const void *a, const void *b, void *arg);
static int32 range_partition_cmp(const void *a, const void *b, void *arg);
@@ -167,6 +222,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
/* Support RelationGetPartitionQual() */
static List *generate_partition_qual(Relation rel, bool recurse);
+/* Support RelationGetPartitionTreeNode() */
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -754,6 +813,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1411,6 +1517,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 07a8d76..6ed8371 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -55,4 +56,8 @@ extern Oid get_partition_parent(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-9.patch (43.6K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-9.patch)
download | inline diff:
From a32262dacb924742e9bba1ca8f8c0840e39bac9e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 343 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 ++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 ++++-
src/backend/executor/nodeModifyTable.c | 142 +++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 71 +++++++
src/backend/optimizer/util/plancat.c | 20 ++-
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 59 ++++++-
src/test/regress/sql/insert.sql | 28 +++
18 files changed, 942 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e742007..8f4dd54 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -226,6 +226,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 PartitionList *make_list_from_spec(PartitionKey key,
PartitionBoundList *list_spec);
@@ -246,6 +258,9 @@ static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1
PartitionRangeBound *b2);
static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2);
static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2);
+static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, PartitionRangeBound *bound);
+static int bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple);
/*
* RelationBuildPartitionDesc
@@ -1536,7 +1551,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;
@@ -1620,6 +1635,267 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ ListInfo *listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->bounds->listinfo != NULL);
+ listinfo = pdesc->bounds->listinfo;
+
+ if (isnull && listinfo->has_null)
+ return listinfo->null_index;
+ else if (!isnull)
+ {
+ found = bsearch_list_values(listinfo->values,
+ listinfo->nvalues,
+ value,
+ key);
+ if (found >= 0)
+ return listinfo->indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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);
+ Assert(pdesc->bounds->rangeinfo != NULL);
+
+ return bsearch_ranges(key, pdesc->nparts,
+ pdesc->bounds->rangeinfo, tuple);
+}
+
/* List partition related support functions */
/*
@@ -1967,3 +2243,68 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2)
return result;
}
+
+/*
+ * bsearch_ranges
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+bsearch_ranges(PartitionKey key, int n, RangeInfo *rangeinfo, Datum *tuple)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = n - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (rangeinfo->ranges[idx]->upper->infinite)
+ {
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->lower))
+ return idx;
+
+ break;
+ }
+ else if (tuple_leftof_bound(key, tuple, rangeinfo->ranges[idx]->upper))
+ {
+ if (rangeinfo->ranges[idx]->lower->infinite)
+ return idx;
+
+ if (tuple_rightof_bound(key, tuple, rangeinfo->ranges[idx]->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, PartitionRangeBound *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, PartitionRangeBound *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 44c273c..8c7e5f2 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.
@@ -1421,6 +1427,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1716,6 +1810,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1729,6 +1825,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);
}
@@ -2279,6 +2392,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2299,7 +2413,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2407,6 +2522,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2434,6 +2550,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;
@@ -2455,10 +2572,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 */
@@ -2510,6 +2663,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 */
@@ -2530,7 +2708,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)
@@ -2560,7 +2747,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),
@@ -2580,6 +2768,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2593,7 +2787,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 329d0b4..3e9b171 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1256,6 +1256,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 5b0e8cf..5e3922f 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,98 @@ 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_TABLE)
+ {
+ 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)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ 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 +2082,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1dbfd78..248569d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3a507ca..7dd6e10 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e11e670..bc5ad4c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 47158f6..476c40f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6152,6 +6152,77 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 6ed8371..08c38b2 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/rel.h"
@@ -60,4 +62,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 697c90f..6e51773 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"
@@ -1141,6 +1142,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-9.patch (24.7K, ../../2e2023f7-e705-6297-bd18-24764ee7c98c@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-9.patch)
download | inline diff:
From 4a21d65cd3fcc6b759ae93e8c3150cfcaa292ce0 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 f43352c..8084029 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 >= 100 AND outletID < 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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 >= DATE '2008-02-01' AND logdate < 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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= 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 >= 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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '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 >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-07 09:27 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-10-07 09:27 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers
It's allowed to specify an non-default opclass in partition by clause,
but I do not see any testcase testing the same. Can you please add
one.
On Fri, Oct 7, 2016 at 2:50 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/10/07 17:33, Rajkumar Raghuwanshi wrote:
>> On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote wrote:
>> I have applied latest patches, getting some error and crash, please check
>> if you are also able to reproduce the same.
>>
>> Observation1 : Not able to create index on partition table.
>> --------------
>> CREATE TABLE rp (c1 int, c2 int) PARTITION BY RANGE(c1);
>> CREATE TABLE rp_p1 PARTITION OF rp FOR VALUES START (1) END (10);
>> CREATE TABLE rp_p2 PARTITION OF rp FOR VALUES START (10) END (20);
>>
>> CREATE INDEX idx_rp_c1 on rp(c1);
>> ERROR: cannot create index on partitioned table "rp"
>
> This one is a new behavior as I mentioned earlier in my previous email.
>
>> Observation2 : Getting cache lookup failed error for multiple column range
>> partition
>> --------------
>> CREATE TABLE rp1_m (c1 int, c2 int) PARTITION BY RANGE(c1, ((c1 + c2)/2));
>>
>> CREATE TABLE rp1_m_p1 PARTITION OF rp1_m FOR VALUES START (1, 1) END (10,
>> 10);
>> ERROR: cache lookup failed for attribute 0 of relation 16429
>>
>> Observation3 : Getting server crash with multiple column range partition
>> --------------
>> CREATE TABLE rp2_m (c1 int, c2 int) PARTITION BY RANGE(((c2 + c1)/2), c2);
>> CREATE TABLE rp2_m_p1 PARTITION OF rp2_m FOR VALUES START (1, 1) END (10,
>> 10);
>> server closed the connection unexpectedly
>> This probably means the server terminated abnormally
>> before or while processing the request.
>> The connection to the server was lost. Attempting reset: Failed.
>> !>
>
> Fixed. Should have been caught when running the regression tests after I
> made changes to 0001 to address some review comments (apparently there was
> no test in 0003 that would've caught this, so added a new one, thanks!).
>
>
> Attached updated patches.
>
> Thanks,
> Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-07 11:49 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-07 11:49 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers
On 2016/10/07 18:27, Ashutosh Bapat wrote:
> It's allowed to specify an non-default opclass in partition by clause,
> but I do not see any testcase testing the same. Can you please add
> one.
OK, I will add some tests related to that.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-25 06:58 Amit Kapila <amit.kapila16@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 2 replies; 329+ messages in thread
From: Amit Kapila @ 2016-10-25 06:58 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/10/05 2:12, Robert Haas wrote:
>> On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>> Even if we leave the empty relfilenode around for now -- in the long
>>>> run I think it should die -- I think we should prohibit the creation
>>>> of subsidiary object on the parent which is only sensible if it has
>>>> rows - e.g. indexes. It makes no sense to disallow non-inheritable
>>>> constraints while allowing indexes, and it could box us into a corner
>>>> later.
>>>
>>> I agree. So we must prevent from the get-go the creation of following
>>> objects on parent tables (aka RELKIND_PARTITIONED_TABLE relations):
>>>
>>> * Indexes
>>> * Row triggers (?)
>>
>> Hmm, do we ever fire triggers on the parent for operations on a child
>> table? Note this thread, which seems possibly relevant:
>>
>> https://www.postgresql.org/message-id/flat/cd282adde5b70b20c57f53bb9ab75e27%40biglumber.com
>
> The answer to your question is no.
>
> The thread you quoted discusses statement-level triggers and the
> conclusion is that they don't work as desired for UPDATE and DELETE on
> inheritance tables. As things stand, only UPDATE or DELETE on the parent
> affects the child tables and it's proposed there that the statement-level
> triggers on the parent and also on any child tables affected should be
> fired in that case.
>
Doesn't that imply that the statement level triggers should be fired
for all the tables that get changed for statement? If so, then in
your case it should never fire for parent table, which means we could
disallow statement level triggers as well on parent tables?
Some of the other things that we might want to consider disallowing on
parent table could be:
a. Policy on table_name
b. Alter table has many clauses, are all of those allowed and will it
make sense to allow them?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-25 11:32 Amit Kapila <amit.kapila16@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Amit Kapila @ 2016-10-25 11:32 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/10/05 2:12, Robert Haas wrote:
> Attached revised patches.
Few assorted review comments for 0001-Catalog*:
1.
@@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
{
..
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
..
}
Why is this restriction? Won't it be useful to allow it for the cases
when user wants to copy the data of all the partitions?
2.
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
/cannot list/cannot use list
3.
@@ -77,7 +77,7 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
} DependencyType;
Why is this change required?
4.
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
Copyright year should be 2016.
5.
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name ('list', 'range', etc.)
etc. in above comment seems to be unnecessary.
6.
+ {PartitionedRelationId, /* PARTEDRELID */
Here PARTEDRELID sounds inconvenient, how about PARTRELID?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 01:06 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-26 01:06 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Thanks for the review!
On 2016/10/25 20:32, Amit Kapila wrote:
> On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> On 2016/10/05 2:12, Robert Haas wrote:
>
>> Attached revised patches.
>
> Few assorted review comments for 0001-Catalog*:
>
>
> 1.
> @@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
> {
> ..
> + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
> + ereport(ERROR,
> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
> + errmsg("cannot copy from partitioned table \"%s\"",
> + RelationGetRelationName(rel)),
> + errhint("Try the COPY (SELECT ...) TO variant.")));
> ..
> }
>
> Why is this restriction? Won't it be useful to allow it for the cases
> when user wants to copy the data of all the partitions?
Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
table is specified, but I thought this may be fine initially.
> 2.
> + if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
> + partnatts > 1)
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
> + errmsg("cannot list partition using more than one column")));
>
> /cannot list/cannot use list
Actually "list partition" works here as a verb, as in "to list partition".
> 3.
> @@ -77,7 +77,7 @@ typedef enum DependencyType
> DEPENDENCY_INTERNAL = 'i',
> DEPENDENCY_EXTENSION = 'e',
> DEPENDENCY_AUTO_EXTENSION = 'x',
> - DEPENDENCY_PIN = 'p'
> + DEPENDENCY_PIN = 'p',
> } DependencyType;
> Why is this change required?
Looks like a leftover hunk from previous revision. Will fix.
> 4.
> @@ -0,0 +1,69 @@
> +/*-------------------------------------------------------------------------
> + *
> + * pg_partitioned_table.h
> + * definition of the system "partitioned table" relation
> + * along with the relation's initial contents.
> + *
> + *
> + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
>
> Copyright year should be 2016.
Oops, will fix.
> 5.
> +/*
> + * PartitionSpec - partition key definition including the strategy
> + *
> + * 'strategy' partition strategy name ('list', 'range', etc.)
>
> etc. in above comment seems to be unnecessary.
Will fix, although I thought that list is yet incomplete.
> 6.
> + {PartitionedRelationId, /* PARTEDRELID */
>
> Here PARTEDRELID sounds inconvenient, how about PARTRELID?
Agreed. There used to be another catalog which had used up PARTRELID, but
that's no longer an issue.
I will include these changes in the next version of patches I will post
soon in reply to [1].
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg%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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 02:00 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Kapila <amit.kapila16@gmail.com>
1 sibling, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-10-26 02:00 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/25 15:58, Amit Kapila wrote:
> On Thu, Oct 6, 2016 at 12:44 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> On 2016/10/05 2:12, Robert Haas wrote:
>>> Hmm, do we ever fire triggers on the parent for operations on a child
>>> table? Note this thread, which seems possibly relevant:
>>>
>>> https://www.postgresql.org/message-id/flat/cd282adde5b70b20c57f53bb9ab75e27%40biglumber.com
>>
>> The answer to your question is no.
>>
>> The thread you quoted discusses statement-level triggers and the
>> conclusion is that they don't work as desired for UPDATE and DELETE on
>> inheritance tables. As things stand, only UPDATE or DELETE on the parent
>> affects the child tables and it's proposed there that the statement-level
>> triggers on the parent and also on any child tables affected should be
>> fired in that case.
>>
>
> Doesn't that imply that the statement level triggers should be fired
> for all the tables that get changed for statement? If so, then in
> your case it should never fire for parent table, which means we could
> disallow statement level triggers as well on parent tables?
I may have misunderstood statement-level triggers, but don't they apply to
tables *specified* as the target table in the statement, instead of those
*changed* by resulting actions?
Now in case of inheritance, unless ONLY is specified, all tables in the
hierarchy including the parent are *implicitly* specified to be affected
by an UPDATE or DELETE operation. So, if some or all of those tables have
any statement-level triggers defined, they should get fired. That was the
conclusion of that thread, but that TODO item still remains [1].
I am not (or no longer) sure how that argument affects INSERT on
partitioned tables with tuple-routing though. Are partitions at all
levels *implicitly specified to be affected* when we say INSERT INTO
root_partitioned_table?
> Some of the other things that we might want to consider disallowing on
> parent table could be:
> a. Policy on table_name
Perhaps. Since there are no rows in the parent table(s) itself of a
partition hierarchy, it might not make sense to continue to allow creating
row-level security policies on them.
> b. Alter table has many clauses, are all of those allowed and will it
> make sense to allow them?
Currently, we only disallow the following with partitioned parent tables
as far as alter table is concerned.
- cannot change inheritance by ALTER TABLE partitioned_table INHERIT ...
- cannot let them be regular inheritance parents either - that is, the
following is disallowed: ALTER TABLE some_able INHERIT partitioned_table
- cannot create UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE constraints
- cannot drop column involved in the partitioning key
Most other forms that affect attributes and constraints follow the regular
inheritance behavior (recursion) with certain exceptions such as:
- cannot add/drop an attribute or check constraint to *only* to/from
the parent
- cannot add/drop NOT NULL constraint to/from *only* the parent
Thoughts?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 02:41 Amit Kapila <amit.kapila16@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Amit Kapila @ 2016-10-26 02:41 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> 1.
>> @@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
>> {
>> ..
>> + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>> + errmsg("cannot copy from partitioned table \"%s\"",
>> + RelationGetRelationName(rel)),
>> + errhint("Try the COPY (SELECT ...) TO variant.")));
>> ..
>> }
>>
>> Why is this restriction? Won't it be useful to allow it for the cases
>> when user wants to copy the data of all the partitions?
>
> Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
> table is specified, but I thought this may be fine initially.
>
Okay, I don't want to add anything to your existing work unless it is
important. However, I think there should be some agreement on which
of the restrictions are okay for first version of patch. This can
avoid such questions in future from other reviewers.
>> 2.
>> + if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
>> + partnatts > 1)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
>> + errmsg("cannot list partition using more than one column")));
>>
>> /cannot list/cannot use list
>
> Actually "list partition" works here as a verb, as in "to list partition".
>
I am not an expert of this matter, so probably some one having better
grip can comment. Are we using something similar in any other error
message?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 02:57 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-10-26 02:57 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/26 11:41, Amit Kapila wrote:
> On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> 1.
>>> @@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
>>> {
>>> ..
>>> + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>>> + ereport(ERROR,
>>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>>> + errmsg("cannot copy from partitioned table \"%s\"",
>>> + RelationGetRelationName(rel)),
>>> + errhint("Try the COPY (SELECT ...) TO variant.")));
>>> ..
>>> }
>>>
>>> Why is this restriction? Won't it be useful to allow it for the cases
>>> when user wants to copy the data of all the partitions?
>>
>> Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
>> table is specified, but I thought this may be fine initially.
>>
>
> Okay, I don't want to add anything to your existing work unless it is
> important. However, I think there should be some agreement on which
> of the restrictions are okay for first version of patch. This can
> avoid such questions in future from other reviewers.
OK, so I assume you don't find this particular restriction problematic in
long term.
>>> 2.
>>> + if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
>>> + partnatts > 1)
>>> + ereport(ERROR,
>>> + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
>>> + errmsg("cannot list partition using more than one column")));
>>>
>>> /cannot list/cannot use list
>>
>> Actually "list partition" works here as a verb, as in "to list partition".
>>
>
> I am not an expert of this matter, so probably some one having better
> grip can comment. Are we using something similar in any other error
> message?
In fact, I changed to the current text after Robert suggested the same [1].
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoaPxXJ14eDVia514UiuQAXyZGqfbz8Qg3G4a8Rz2gKF7w%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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 03:09 Amit Kapila <amit.kapila16@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Kapila @ 2016-10-26 03:09 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Oct 26, 2016 at 8:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/10/26 11:41, Amit Kapila wrote:
>> On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>> 1.
>>>> @@ -1775,6 +1775,12 @@ BeginCopyTo(ParseState *pstate,
>>>> {
>>>> ..
>>>> + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>>>> + ereport(ERROR,
>>>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>>>> + errmsg("cannot copy from partitioned table \"%s\"",
>>>> + RelationGetRelationName(rel)),
>>>> + errhint("Try the COPY (SELECT ...) TO variant.")));
>>>> ..
>>>> }
>>>>
>>>> Why is this restriction? Won't it be useful to allow it for the cases
>>>> when user wants to copy the data of all the partitions?
>>>
>>> Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
>>> table is specified, but I thought this may be fine initially.
>>>
>>
>> Okay, I don't want to add anything to your existing work unless it is
>> important. However, I think there should be some agreement on which
>> of the restrictions are okay for first version of patch. This can
>> avoid such questions in future from other reviewers.
>
> OK, so I assume you don't find this particular restriction problematic in
> long term.
>
I think you can keep it as you have in patch. After posting your
updated patches, please do send a list of restrictions which this
patch is imposing based on the argument that for first version they
are not essential.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 05:26 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-26 05:26 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/26 12:09, Amit Kapila wrote:
> On Wed, Oct 26, 2016 at 8:27 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> On 2016/10/26 11:41, Amit Kapila wrote:
>>> On Wed, Oct 26, 2016 at 6:36 AM, Amit Langote
>>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>>
>>>> Sure, CopyTo() can be be taught to scan leaf partitions when a partitioned
>>>> table is specified, but I thought this may be fine initially.
>>>
>>> Okay, I don't want to add anything to your existing work unless it is
>>> important. However, I think there should be some agreement on which
>>> of the restrictions are okay for first version of patch. This can
>>> avoid such questions in future from other reviewers.
>>
>> OK, so I assume you don't find this particular restriction problematic in
>> long term.
>
> I think you can keep it as you have in patch. After posting your
> updated patches, please do send a list of restrictions which this
> patch is imposing based on the argument that for first version they
> are not essential.
OK, agreed that it will be better to have all such restrictions and
limitations of the first version listed in one place, rather than being
scattered across different emails where they might have been mentioned and
discussed.
I will try to include such a list when posting the latest set of patches.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 08:57 Amit Kapila <amit.kapila16@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
2 siblings, 1 reply; 329+ messages in thread
From: Amit Kapila @ 2016-10-26 08:57 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Oct 5, 2016 at 7:20 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> [ latest patch set ]
>
> Reviewing 0003:
>
>
> 5. I wonder how well this handles multi-column partition keys. You've
> just got one Datum flag and one is-finite flag per partition, but I
> wonder if you don't need to track is-finite on a per-column basis, so
> that you could partition on (a, b) and have the first partition go up
> to (10, 10), the second to (10, infinity), the third to (20, 10), the
> fourth to (20, infinity), and the last to (infinity, infinity). FWIW,
> Oracle supports this sort of thing, so perhaps we should, too. On a
> related note, I'm not sure it's going to work to treat a composite
> partition key as a record type. The user may want to specify a
> separate opfamily and collation for each column, not just inherit
> whatever the record behavior is. I'm not sure if that's what you are
> doing, but the relcache structures don't seem adapted to storing one
> Datum per partitioning column per partition, but rather just one Datum
> per partition, and I'm not sure that's going to work very well.
>
@@ -123,6 +123,9 @@ typedef struct RelationData
{
..
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
..
}
I think one thing to consider here is the increase in size of relcache
due to PartitionDescData. I think it will be quite useful in some of
the cases like tuple routing. Isn't it feasible to get it in some
other way, may be by using relpartbound from pg_class tuple?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 09:34 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-10-26 09:34 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/26 17:57, Amit Kapila wrote:
> @@ -123,6 +123,9 @@ typedef struct RelationData
> {
> ..
> MemoryContext rd_partkeycxt; /* private memory cxt for the below */
> struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
> + MemoryContext rd_pdcxt; /* private context for partdesc */
> + struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
> + List *rd_partcheck; /* partition CHECK quals */
> ..
> }
>
> I think one thing to consider here is the increase in size of relcache
> due to PartitionDescData. I think it will be quite useful in some of
> the cases like tuple routing. Isn't it feasible to get it in some
> other way, may be by using relpartbound from pg_class tuple?
Whereas pg_class.relpartbound stores partition bound of the *individual
partitions* in Node form, the above relcache struct is associated with
parent tables; it contains some efficient to use (and fairly compact)
representation of bounds of *all* the partitions of the parent. Consider
for example, an array of sorted range bounds for range partitioned tables.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 10:12 Amit Kapila <amit.kapila16@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Kapila @ 2016-10-26 10:12 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Oct 26, 2016 at 3:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/10/26 17:57, Amit Kapila wrote:
>> @@ -123,6 +123,9 @@ typedef struct RelationData
>> {
>> ..
>> MemoryContext rd_partkeycxt; /* private memory cxt for the below */
>> struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
>> + MemoryContext rd_pdcxt; /* private context for partdesc */
>> + struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
>> + List *rd_partcheck; /* partition CHECK quals */
>> ..
>> }
>>
>> I think one thing to consider here is the increase in size of relcache
>> due to PartitionDescData. I think it will be quite useful in some of
>> the cases like tuple routing. Isn't it feasible to get it in some
>> other way, may be by using relpartbound from pg_class tuple?
>
> Whereas pg_class.relpartbound stores partition bound of the *individual
> partitions* in Node form, the above relcache struct is associated with
> parent tables; it contains some efficient to use (and fairly compact)
> representation of bounds of *all* the partitions of the parent.
>
Okay, but still it will be proportional to number of partitions and
the partition keys. Is it feasible to store ranges only for
partitions that are actively accessed? For example, consider a table
with 100 partitions and the first access to table requires to access
5th partition, then we store ranges for first five partitions or
something like that. This could be helpful, if we consider cases that
active partitions are much less as compare to total partitions of a
table.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 18:04 Robert Haas <robertmhaas@gmail.com>
parent: Amit Kapila <amit.kapila16@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-10-26 18:04 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Oct 26, 2016 at 6:12 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
> On Wed, Oct 26, 2016 at 3:04 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> On 2016/10/26 17:57, Amit Kapila wrote:
>>> @@ -123,6 +123,9 @@ typedef struct RelationData
>>> {
>>> ..
>>> MemoryContext rd_partkeycxt; /* private memory cxt for the below */
>>> struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
>>> + MemoryContext rd_pdcxt; /* private context for partdesc */
>>> + struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
>>> + List *rd_partcheck; /* partition CHECK quals */
>>> ..
>>> }
>>>
>>> I think one thing to consider here is the increase in size of relcache
>>> due to PartitionDescData. I think it will be quite useful in some of
>>> the cases like tuple routing. Isn't it feasible to get it in some
>>> other way, may be by using relpartbound from pg_class tuple?
>>
>> Whereas pg_class.relpartbound stores partition bound of the *individual
>> partitions* in Node form, the above relcache struct is associated with
>> parent tables; it contains some efficient to use (and fairly compact)
>> representation of bounds of *all* the partitions of the parent.
>>
>
> Okay, but still it will be proportional to number of partitions and
> the partition keys. Is it feasible to store ranges only for
> partitions that are actively accessed? For example, consider a table
> with 100 partitions and the first access to table requires to access
> 5th partition, then we store ranges for first five partitions or
> something like that. This could be helpful, if we consider cases that
> active partitions are much less as compare to total partitions of a
> table.
I have serious doubt about whether it's a good idea to do that EVER,
but it certainly doesn't need to be in the first version of this
patch.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 18:06 Robert Haas <robertmhaas@gmail.com>
parent: Amit Kapila <amit.kapila16@gmail.com>
1 sibling, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-10-26 18:06 UTC (permalink / raw)
To: Amit Kapila <amit.kapila16@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Oct 25, 2016 at 2:58 AM, Amit Kapila <amit.kapila16@gmail.com> wrote:
> a. Policy on table_name
No, because queries against the parent will apply the policy to
children. See today's commit
162477a63d3c0fd1c31197717140a88077a8d0aa.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 18:13 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-10-26 18:13 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Oct 25, 2016 at 9:06 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> I will include these changes in the next version of patches I will post
> soon in reply to [1].
> [1]
> https://www.postgresql.org/message-id/CA%2BTgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg%40mail.g...
How soon? Tempus fugit, and tempus in this release cycle fugit
particularly quickly. It looks like the last revision of these
patches was on October 7th, and that's now more than 2 weeks ago. We
need to get moving here if this is going to happen this cycle.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-26 18:31 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-10-26 18:31 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Oct 25, 2016 at 10:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> I am not (or no longer) sure how that argument affects INSERT on
> partitioned tables with tuple-routing though. Are partitions at all
> levels *implicitly specified to be affected* when we say INSERT INTO
> root_partitioned_table?
I'd say yes.
>> Some of the other things that we might want to consider disallowing on
>> parent table could be:
>> a. Policy on table_name
>
> Perhaps. Since there are no rows in the parent table(s) itself of a
> partition hierarchy, it might not make sense to continue to allow creating
> row-level security policies on them.
No, per my previous email. Those policies are emphatically not without effect.
>> b. Alter table has many clauses, are all of those allowed and will it
>> make sense to allow them?
>
> Currently, we only disallow the following with partitioned parent tables
> as far as alter table is concerned.
>
> - cannot change inheritance by ALTER TABLE partitioned_table INHERIT ...
>
> - cannot let them be regular inheritance parents either - that is, the
> following is disallowed: ALTER TABLE some_able INHERIT partitioned_table
>
> - cannot create UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE constraints
>
> - cannot drop column involved in the partitioning key
>
> Most other forms that affect attributes and constraints follow the regular
> inheritance behavior (recursion) with certain exceptions such as:
>
> - cannot add/drop an attribute or check constraint to *only* to/from
> the parent
>
> - cannot add/drop NOT NULL constraint to/from *only* the parent
>
> Thoughts?
Seems sensible to me.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-27 02:08 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-27 02:08 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Kapila <amit.kapila16@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/27 3:13, Robert Haas wrote:
> On Tue, Oct 25, 2016 at 9:06 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> I will include these changes in the next version of patches I will post
>> soon in reply to [1].
>> [1]
>> https://www.postgresql.org/message-id/CA%2BTgmoYJcUTcN7vVgg54GHtffH11JJWYZnfF4KiRxjV-iaACQg%40mail.g...
>
> How soon? Tempus fugit, and tempus in this release cycle fugit
> particularly quickly. It looks like the last revision of these
> patches was on October 7th, and that's now more than 2 weeks ago. We
> need to get moving here if this is going to happen this cycle.
Sorry about the delay, I'll post no later than tomorrow.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-10-28 07:53 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
2 siblings, 7 replies; 329+ messages in thread
From: Amit Langote @ 2016-10-28 07:53 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/10/05 10:50, Robert Haas wrote:
> On Tue, Oct 4, 2016 at 4:02 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> [ latest patch set ]
>
> Reviewing 0003:
Thanks a lot for the review and sorry about the delay in replying.
> + This form attaches an existing table (partitioned or otherwise) as
>
> (which might itself be partitioned)
>
> + partition of the target table. Partition bound specification must
> + correspond with the partition method and the key of the target table.
>
> The partition bound specification must correspond to the partitioning
> method and partitioning key of the target table.
>
> + The table being attached must have all the columns of the target table
> + with matching types and no more. Also, it must have all the matching
>
> The table to be attached must have all of the same columns as the
> target table and no more; moreover, the column types must also match.
Done.
> + with matching types and no more. Also, it must have all the matching
> + constraints as the target table. That includes both <literal>NOT NULL</>
> + and <literal>CHECK</> constraints. If some <literal>CHECK</literal>
> + constraint of the table being attached is marked <literal>NO
> INHERIT</literal>,
> + the command will fail; such constraints must either be dropped or
> + recreated without the <literal>NO INHERIT</literal> mark.
>
> Why all of these requirements?
Except the last one, regular inheritance currently has the same
requirements; from the docs:
"""
To be added as a child, the target table must already contain all the same
columns as the parent (it could have additional columns, too). The columns
must have matching data types, and if they have NOT NULL constraints in
the parent then they must also have NOT NULL constraints in the child.
There must also be matching child-table constraints for all CHECK
constraints of the parent, except those marked non-inheritable (that is,
created with ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT) in the parent,
which are ignored; all child-table constraints matched must not be marked
non-inheritable.
"""
Note that the "(it could have additional columns, too)" part does not
apply for partitions. Also the last point does not hold for partitioned
tables because they never have NO INHERIT constraints.
> We could instead perform a scan to
> validate that the constraints are met. I think the way this should
> work is:
>
> 1. ATTACH PARTITION works whether matching NOT NULL and CHECK
> constraints are present or not.
Wouldn't this break the invariant that if the parent has a NOT NULL or a
CHECK constraint, all of its children must have it too? Remember that we
allow directly inserting/updating data into partitions, so these
constraints better be persistent.
> 2. If all of the constraints are present, and a validated constraint
> matching the implicit partitioning constraint is also present, then
> ATTACH PARTITION does not scan the table to validate constraints;
> otherwise, it does.
I wonder what matching in "validated constraint matching with the implicit
partitioning constraint" means? By that do you mean an exact equal()
match, provided both have been suitably canonicalized? Or an existing
check constraint that *implies* the partitioning constraint?
> 3. NO VALIDATE is not an option.
I had my doubts about this option initially, but 2 above seemed hard to
do. Or perhaps we can just remove this option and we will always scan
when attaching a partition.
> + Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
> + <literal>FOREIGN KEY</literal> constraints are not considered, but that
> + might change in the future.
>
> Really? Changing that sounds impractical to me.
Assuming you are questioning the "but that might change in the future"
part, I took that line from the description for INHERIT. Maybe it was
written in that section with a view that someone might implement globally
enforced variant of each of those constraints. I thought the path to that
would be slightly easier with declarative partitioning but that may be too
optimistic. Removed that part.
> + This form detaches specified partition of the target table. The
> + detached partition continues to exist as a standalone table with no ties
> + remaining with the target table.
>
> continues to exist as a standalone table, but no longer has any ties
> to the table from which it was detached.
Done.
>
> + Note that if a partition being detached is itself a partitioned table,
> + it continues to exist as such.
>
> You don't really need to say this, I think. All of the properties of
> the detached table are retained, not only its partitioning status.
> You wouldn't like it if I told you to document "note that if a
> partition being detached is unlogged, it will still be unlogged".
I thought same as Petr who replied to this part of your email - might help
avoid confusion. But it might be redundant, so I removed that line.
>
> To add the table as a new child of a parent table, you must own the
> - parent table as well.
> + parent table as well. That applies to both adding the table as a
> + inheritance child of a parent table and attaching a table as partition to
> + the table.
>
> To add the table as a new child of a parent table, or as a new
> partition of a partitioned table, you must own the parent table as
> well.
Done.
> + The name of the table to attach as a new partition to or
> detach from this table.
>
> s/to or/or to/
Oops, fixed.
> + <literal>NO VALIDATE</> option is spcified.
>
> Typo, but see comments above about nuking this altogether.
Fixed the typo for now. As I said above it's not clear to me how the
alternative method of skipping the scan is supposed to work.
> A recursive <literal>DROP COLUMN</literal> operation will remove a
> descendant table's column only if the descendant does not inherit
> that column from any other parents and never had an independent
> - definition of the column. A nonrecursive <literal>DROP
> + definition of the column (which always holds if the descendant table
> + is a partition). A nonrecursive <literal>DROP
> COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
> COLUMN</command>) never removes any descendant columns, but
> - instead marks them as independently defined rather than inherited.
> + instead marks them as independently defined rather than inherited,
> + unless the descendant table is a partition.
>
> This is a hairy explanation. I suggest that the documentation say
> this (and that the code implement it): A nonrecursive DROP TABLE
> command will fail for a partitioned table, because all partitions of a
> table must have the same columns as the partitioning root.
Agreed, done (including the code).
> - that are not marked <literal>NO INHERIT</>.
> + that are not marked <literal>NO INHERIT</> which are unsupported if
> + the table is a partitioned table.
>
> I think you can omit this hunk.
Done.
>
> + 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.
>
> I'd delete the sentence beginning with "However".
Done.
> + 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</>:
>
> s/, that is/ as a/
Fixed.
> +<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> }
>
> I think you can inline the definitions of list_spec and range_spec
> here instead of making them separate productions, and I think that
> would be preferable.
>
> FOR VALUES { IN ( <replaceable
> class="PARAMETER">expression</replaceable> [, ...] ) |
> START <replaceable class="PARAMETER">lower-bound</replaceable> [
> INCLUSIVE | EXCLUSIVE ] END <replaceable
> class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
> }
Ah, done that way.
>
> + parent table (name optionally schema-qualified).
>
> Parenthetical phrase isn't needed.
Removed.
>
> + 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.
>
> The partition bound specification must correspond to the partitioning
> method and partitioning key of the parent table, and must not overlap
> with any existing partition of that parent.
Done.
> + clause, if any. Defaults and constraints can optionally be specified
> + for each of the inherited columns, which override those in the parent.
>
> Surely not. You can't "override" an inherited constraint or an
> inherited default. The child may have extra constraints not present
> in the parent, and may have different defaults when it is directly
> targeted by an insertion, but it can't possibly override the parent
> defaults.
Removed ", 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.
>
> The first part of this seems right, but then what's going on with the
> reference to constraints in the previous sentence? (See previous
> review comment.) The second sentence I would delete (but see below).
In the previous sentence, I meant the constraints that one can specify
using WITH OPTIONS [ column_constraint [...] ], but I removed
the "overrides those in the parent" part as mentioned.
Removed the second sentence and instead adopted your text below.
>
> + <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>
>
> How about: Rows inserted into a partitioned table will be
> automatically routed to the correct partition. If no suitable
> partition exists, an error will occur.
Much better, done.
> + <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>
>
> How about: A partition must have the same column names and types as
> the table of which it is a partition. Therefore, modifications the
> column names or types of the partitioned table will automatically
> propagate to all children, as will operations such as TRUNCATE which
> normally affect a table and all of its inheritance children. It is
> also possible to TRUNCATE a partition individually, just as for an
> inheritance child.
Done.
> Insisting that you can't drop a child without detaching it first seems
> wrong to me. If I already made this comment and you responded to it,
> please point me back to whatever you said. However, my feeling is
> this is flat wrong and absolutely must be changed.
I said the following [1]:
| Hmm, I don't think I like this. Why should it be necessary to detach
| a partition before dropping it? That seems like an unnecessary step.
I thought we had better lock the parent table when removing one of its
partitions and it seemed a bit odd to lock the parent table when dropping
a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
PARTITION, the parent table is locked anyway.
> - if (is_no_inherit)
> +
> + /* Discard the NO INHERIT flag if the relation is a partition */
> + if (is_no_inherit && !rel->rd_rel->relispartition)
>
> Something about this seems fishy. In StoreRelCheck(), you disallow
> the addition of a NO INHERIT constraint on a partition, but here you
> seem happy to accept it and ignore the NO INHERIT property. That
> doesn't seem consistent. We should adopt a consistent policy about
> what to do about such constraints, and I submit that throwing an error
> is better than silently changing things, unless you have some reason
> for doing that which I'm not seeing. Anyway, we should have the same
> behavior in both cases.
Allowing to add NO INHERIT constraints to (leaf) partitions does not seem
like it will cause any problems not that the flag will have any meaning.
It will come into play only in one case - in the form of causing an error
if a constraint with the same name is added to the parent.
With that in mind, I removed the check in StoreRelCheck() that you mention
here and also removed the above hunk to which your comment was addressed.
> We should also standardize on what value of conislocal and coninhcount
> children end up with; presumably the latter should be 1, but I'm not
> sure if the former should be true or false. In either case, anything
> that can vary between children probably needs to be dumped, so let's
> enforce that it doesn't so we don't have to dump it. I'm not sure
> whether the code here achieves those objectives, though, and note the
> comment in the function header about making sure the logic here
> matches MergeConstraintsIntoExisting.
To summarize:
If a table is partition, it doesn't have any local attributes. Also, it
doesn't have any check constraint that is both local *and* inherited.
Only the non-inherited constraints are ever local.
So, all parent attributes will be present present in all the partitions at
any given time with attislocal = false and attinhcount = 1. Likewise for
inherited constraints with conislocal = false and coninhcount = 1.
With above rules in place, pg_dump no longer dumps attributes or inherited
constraints in CREATE TABLE commands of individual partitions.
> I think the overriding principle here should be: If you attach a table
> as a partition, it must not be part of a standard inheritance
> hierarchy, and it must not be a partition of any other table. It can,
> however, be partitioned itself. If you later detach a partition, it
> ends up as a standalone table with a copy of each constraint it had as
> a partition - probably including the implicit partition constraint.
> The DBA can drop those constraints if they're not wanted.
Check. About implicit constraints, see below.
> I wonder if it's really a good idea for the partition constraints to
> be implicit; what is the benefit of leaving those uncatalogued?
I did start out that way - ie, catalogued implicit constraints, but later
thought it might not be good to end up with multiple copies of essentially
the same information. With cataloguing them will come dependencies and
all places that know about pg_constraint.
In the long term, I think we're only going to need them because we want to
enforce them when directly inserting data into partitions.
So, detaching a partition does not emit a check constraint matching the
implicit partition constraint.
> + * Depending on whether the relation in question is list or range
> + * partitioned, one of the fields is set.
> + */
> +typedef struct BoundCollectionData
> +{
> + struct ListInfo *listinfo;
> + struct RangeInfo *rangeinfo;
> +} BoundCollectionData;
>
> This seems like an odd design. First, when you have a pointer to
> either of two things, the normal tool for that in C would be a union,
> not a struct. Second, in PostgreSQL we typically handle that by making
> both of the things nodes and then you can use IsA() or switch on
> nodeTag() to figure out what you've got. Third, the only place this
> is used at least in 0003 is as part of PartitionDescData, which only
> has 3 members, so if you were going to do it with 2 members, you could
> just include these two members directly. Considering all of the
> foregoing, I'd suggest making this a union and including partstrategy
> in PartitionDescData.
>
> I think that the names ListInfo and RangeInfo are far too generic for
> something that's specific to partitioning.
How about this:
/*
* Collection of bounds of a partitioned relation (either physical or
* logical)
*/
typedef struct BoundCollectionData
{
char strategy; /* list or range bounds? */
union
{
struct PartitionListInfo lists;
struct PartitionRangeInfo ranges;
} bounds;
} BoundCollectionData;
I added the strategy field here instead of PartitionDescData, because this
structure is supposed to represent even the transient partitioned
relations. If the relation is a physical relation we can determine
strategy from PartitionKey.
I renamed ListInfo/RangeInfo to PartitionListInfo/PartitionRangeInfo.
> +/*
> + * Range bound collection - sorted array of ranges of partitions of a range
> + * partitioned table
> + */
> +typedef struct RangeInfo
> +{
> + struct PartitionRange **ranges;
> +} RangeInfo;
> +
> +/* One partition's range */
> +typedef struct PartitionRange
> +{
> + struct PartitionRangeBound *lower;
> + struct PartitionRangeBound *upper;
> +} PartitionRange;
>
> This representation doesn't seem optimal, because in most cases the
> lower bound of one partition will be the upper bound of the next. I
> suggest that you flatten this representation into a single list of
> relevant bounds, each flagged as to whether it is exclusive and
> whether it is finite; and a list with one more element of bounds. For
> example, suppose the partition bounds are [10, 20), [20, 30), (30,
> 40), and [50, 60). You first construct a list of all of the distinct
> bounds, flipping inclusive/exclusive for the lefthand bound in each
> case. So you get:
>
> 10 EXCLUSIVE
> 20 EXCLUSIVE
> 30 EXCLUSIVE
> 30 INCLUSIVE
> 40 EXCLUSIVE
> 50 EXCLUSIVE
> 60 EXCLUSIVE
>
> When ordering items for this list, if the same item appears twice, the
> EXCLUSIVE copy always appears before INCLUSIVE. When comparing
> against an EXCLUSIVE item, we move to the first half of the array if
> we are searching for a value strictly less than that value; when
> comparing against an INCLUSIVE item, we move to the first half of the
> array if we are searching for a value less than or equal to that
> value.
>
> This is a list of seven items, so a binary search will return a
> position between 0 (less than all items) and 7 (greater than all
> items). So we need a list of 8 partition mappings, which in this case
> will look like this: -1, 0, 1, -1, 2, -1, 3, -1.
>
> In this particular example, there are only two adjacent partitions, so
> we end up with 7 bounds with this representation vs. 8 with yours, but
> in general I think the gains will be bigger. If you've got 1000
> partitions and they're all adjacent, which is likely, you store 1000
> bounds instead of 2000 bounds by doing it this way.
Thanks for the idea. I have implemented the same.
>
> + * Note: This function should be called only when it is known that 'relid'
> + * is a partition.
>
> Why? How about "Because this function assumes that the relation whose
> OID is passed as an argument will have precisely one parent, it should
> only been called when it is known that the relation is a partition."
Rewrote the note.
> + /*
> + * Translate vars in the generated expression to have correct attnos.
> + * Note that the vars in my_qual bear attnos dictated by key which carries
> + * physical attnos of the parent. We must allow for a case where physical
> + * attnos of a partition can be different from the parent.
> + */
> + partexprs_item = list_head(key->partexprs);
> + for (i = 0; i < key->partnatts; i++)
> + {
> + AttrNumber attno = key->partattrs[i],
> + new_attno;
> + char *attname;
> +
> + if (attno != 0)
> + {
> + /* Simple column reference */
> + attname = get_attname(RelationGetRelid(parent), attno);
> + new_attno = get_attnum(RelationGetRelid(rel), attname);
> +
> + if (new_attno != attno)
> + my_qual = (List *) translate_var_attno((Node *) my_qual,
> + attno,
> + new_attno);
>
> It can't really be safe to do this one attribute number at a time, or
> even if by some good fortune it can't be broken, it at least it seems
> extremely fragile. Suppose that you translate 0 -> 3 and then 3 -> 5;
> now the result is garbage. It's not very efficient to do this one
> attno at a time, either.
You are quite right. I changed this to us map_variable_attnos().
When doing that, I noticed that the above function does not map whole-row
references but instead just returns whether one was encountered, so that
the caller can output an error that expressions containing whole-row vars
are not allowed in the corresponding context (examples of such callers
include transformTableLikeClause()). In context of this function, there
is no way we could output such error.
Thinking more about that, even if we did map whole-row vars in check
expressions, constraint exclusion code would not be able to handle them,
because it does not work with anything but Const node as predicate's or
query clause's right-operand.
I went ahead and prohibited whole-row references from being used in the
partition key expressions in the first place (ie, in patch 0001).
>
> + if (classform->relispartition)
> + ereport(ERROR,
> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
> + errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
> + get_rel_name(get_partition_parent(relOid))),
> + errhint("Use ALTER TABLE DETACH PARTITION to be able
> to drop it.")));
> +
>
> RangeVarCallbackForDropRelation should do only minimal sanity checks;
> defer this until after we have a relation lock.
Moved this check to RemoveRelations(), wherein after
RangeVarGetRelidExtended() returns, the relation is guaranteed to be locked.
> I didn't get all the way through this patch, so this is a pretty
> incomplete review, but it's late here and I'm out of steam for
> tonight. Some general comments:
>
> 1. I think that this patch seems to introduce an awful lot of new
> structures with confusingly similar names and purposes:
> PartitionBoundList, PartitionBoundRange, ListInfo, RangeInfo,
> PartitionRange, PartitionList, ListValue, RangePartition. You've got
> 4 different structures here that all have "Partition" and "Range" in
> the name someplace, including both PartitionRange and RangePartition.
> Maybe there's no way around that kind of duplication; after all there
> are quite a few moving parts here. But it seems like it would be good
> to try to simplify it.
OK, I got rid of most of those structs and now there are only the
following: PartitionListInfo, PartitionRangeInfo, PartitionRangeBound and
PartitionListValue.
> 2. I'm also a bit concerned about the fairly large amount of
> apparently-boilerplate code in partition.c, all having to do with how
> we create all of these data structures and translate between different
> forms of them. I haven't understood that stuff thoroughly enough to
> have a specific idea about how we might be able to get rid of any of
> it, and maybe there's no way. But that too seems like a topic for
> futher investigation. One idea is that maybe some of these things
> should be nodes that piggyback on the existing infrastructure in
> src/backend/nodes instead of inventing a new way to do something
> similar.
I thought about the idea of using the src/backend/nodes infrastructure for
partitioning data structures. However, none of those structures are part
of any existing Node nor do I imagine they will be in future, so making
them a Node seems unnecessary. But then there is a subset of NodeTags
called "TAGS FOR RANDOM OTHER STUFF" which are Node objects that not part
of parse/plan/execute node tree structures such as T_TriggerData,
T_ForeignKeyCacheInfo. I wonder if you meant to make partitioning
structures nodes of that category.
That said, I have tried in the current patch to reduce the amount of
unnecessary boilerplate code.
> 3. There are a lot of places where you have separate code for the
> range and list partitioning cases, and I'm suspicious that there are
> ways that code could be unified. For example, with list partitioning,
> you have a bunch of Datums which represent the specific values that
> can appear in the various partitions, and with range partitioning, you
> have a bunch of Datums that represent the edges of partitions. Well,
> if you used the same array for both purposes, you could use the same
> code to copy it. That would involve flattening away a lot of the
> subsidiary structure under PartitionDescData and pulling up stuff that
> is buried lower down into the main structure, but I think that's
> likely a good idea anyway - see also point #1.
Hmm, I think it might be somewhat clearer to keep them separate in the
form of PartitionListInfo and PartitionRangeInfo structs. In case of
range partitioning, each individual range bound is actually a
PartitionRangeBound instance, not just a Datum. That's because we have to
store for each range bound the following information: an array of Datums
to represent the composite partition key, is-finite flags for each
partitioning column, a flag representing whether it is a inclusive bound,
and a flag representing whether it is lower or upper bound.
> 4. I'm somewhat wondering if we ought to just legislate that the lower
> bound is always inclusive and the upper bound is always exclusive.
> The decision to support both inclusive and exclusive partition bounds
> is responsible for an enormous truckload of complexity in this patch,
> and I have a feeling it is also going to be a not-infrequent cause of
> user error.
I thought we decided at some point to go with range type like notation to
specify range partition bound because of its flexibility. I agree though
that with that flexibility, there will more input combinations that will
cause error. As for the internal complexity, it's not clear to me whether
it will be reduced by always-inclusive lower and always-exclusive upper
bounds. We would still need to store the inclusive flag with individual
PartitionRangeBound and consider it when comparing them with each other
and with partition key of tuples.
> 5. I wonder how well this handles multi-column partition keys. You've
> just got one Datum flag and one is-finite flag per partition, but I
> wonder if you don't need to track is-finite on a per-column basis, so
> that you could partition on (a, b) and have the first partition go up
> to (10, 10), the second to (10, infinity), the third to (20, 10), the
> fourth to (20, infinity), and the last to (infinity, infinity). FWIW,
> Oracle supports this sort of thing, so perhaps we should, too. On a
I have implemented the feature that allows specifying UNBOUNDED per column.
> related note, I'm not sure it's going to work to treat a composite
> partition key as a record type. The user may want to specify a
> separate opfamily and collation for each column, not just inherit
> whatever the record behavior is. I'm not sure if that's what you are
> doing, but the relcache structures don't seem adapted to storing one
> Datum per partitioning column per partition, but rather just one Datum
> per partition, and I'm not sure that's going to work very well.
Actually, there *is* one Datum per partitioning column. That is,
composite partition key is not treated as a record as it may have seemed.
Please find attached the latest version of the patches taking care of the
above review comments and some other issues I found. As Amit Kapila
requested [2], here is a list of restrictions on partitioned tables, of
which some we might be able to overcome in future:
- cannot create indexes
- cannot create row triggers
- cannot specify UNIQUE, PRIMARY KEY, FOREIGN KEY, EXCLUDE constraints
- cannot become inheritance parent or child
- cannot use in COPY TO command
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/169708f6-6e5a-18d1-707b-1b323e4a6baf%40lab.ntt.co.jp
[2]
https://www.postgresql.org/message-id/CAA4eK1LqTqZkPSoonF5_cOz94OUZG9j0PNfLdhi_nPtW82fFVA%40mail.gma...
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-10.patch (113.8K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-10.patch)
download | inline diff:
From a9e30554e62a978db973364ef8e515bbc1578b30 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 09:59:15 +0900
Subject: [PATCH 1/9] Catalog and DDL for partitioned tables.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 160 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 9 +-
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 | 446 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 +++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 11 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 257 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 3 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 36 +++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 66 ++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 46 +++
src/test/regress/expected/create_table.out | 163 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 34 +++
src/test/regress/sql/create_table.sql | 142 +++++++++
51 files changed, 1936 insertions(+), 48 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..ccc2b6b 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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>partcollation</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 collation to use for partitioning.
+ </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>partattrs</>. 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..893c899 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,41 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partitioning key</firstterm>
+ for the table. When using range partitioning, the partitioning key can
+ include multiple columns or expressions, but for list partitioning, the
+ partitioning key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is routed
+ to a partition based on the value of columns or expressions in the
+ partition key. If no existing partition matches the values in the new
+ row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..31a7bb4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -3018,3 +3041,132 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * 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,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition key, opclass info into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references which will count as self-dependency items; in this case,
+ * the depender is the table itself (there is no such thing as partition
+ * key object).
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b4140eb..0ef590a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9f90e62 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1256,7 +1259,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 a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2137372..c2ae3f8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -710,6 +730,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process and store partition key information, if any */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -926,7 +973,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1003,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1353,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1582,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from table \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2230,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4360,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4597,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5488,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5764,69 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * 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 *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (is_expr)
+ *is_expr = false;
+ if (attnum == partattno)
+ return true;
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ if (is_expr)
+ *is_expr = true;
+
+ /* Find all attributes referenced */
+ 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;
+
+ if (attno == attnum)
+ return true;
+ }
+ partexprs_item = lnext(partexprs_item);
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5840,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 +5885,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 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7883,6 +8038,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7913,6 +8069,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7928,7 +8097,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8955,6 +9125,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9417,6 +9588,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9839,7 +10011,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10038,6 +10211,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10089,6 +10267,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from \"%s\"", parent->relname),
+ errdetail("Table \"%s\" is partitioned.", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11478,6 +11663,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11927,7 +12113,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12081,6 +12267,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12092,3 +12279,248 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partition strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears 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_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+
+ /* Note that whole-row references can't happen here; see below */
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ Bitmapset *expr_attrs = NULL;
+
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+
+ /* Cannot have expressions containing whole-row references */
+ pull_varattnos(expr, 1, &expr_attrs);
+ if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition key expressions cannot contain whole-row references")));
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 9de22a1..133776d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1112,6 +1120,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1227,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71714bc..f283a97 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3018,6 +3018,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4174,6 +4175,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5088,6 +5116,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 29a090f..a6421d2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2634,6 +2635,28 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3387,6 +3410,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ae86954..417e20a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3267,6 +3268,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3852,6 +3874,12 @@ outNode(StringInfo str, const void *obj)
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..9d32a20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -541,6 +543,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2808,69 +2814,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3427,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..9cb9222 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_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +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_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..e46f879 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partition key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4267,6 +4518,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5288,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..9f9ee5e
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* 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 */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..c7b0af3 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 88297bb..65d0009 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -453,6 +453,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6de2cab..8cc41cf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,41 @@ 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 *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy name (ie, 'list' and 'range')
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1753,6 +1788,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..f7c0ab0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,33 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Partition key information
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ List *partexprs; /* partition key expressions, if any */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation */
+ Oid *partcollation;
+
+ /* Type information of partition attributes */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +121,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +562,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * Partition key information inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * Partition key information inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index a1ab823..2c913e5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,49 @@ Table "public.test_add_column"
c4 | integer |
DROP TABLE test_add_column;
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: relation "no_drop_alter_partexpr" does not exist
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: relation "no_drop_alter_partcol" does not exist
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+ERROR: cannot change inheritance of partitioned table
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..c6ed153 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,166 @@ 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 list partition using more than one column
+-- 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: set-returning functions are not allowed in partition key expression
+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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partition strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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
+-- 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);
+-- cannot contain whole-row references
+CREATE TABLE fail_wholerow_ref (
+ a int
+) PARTITION BY RANGE ((fail_wholerow_ref));
+ERROR: partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE 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 a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+DROP TABLE check_relkind;
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table dependency_matters depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+ERROR: cannot inherit from table "no_inh_parted"
+DETAIL: Table "no_inh_parted" is partitioned.
+DROP TABLE no_inh_parted;
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..3dbeb48 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,37 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_or_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_or_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_child (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE inh_parent(a int);
+ALTER TABLE no_inh_child INHERIT inh_parent;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..6a2f8f6 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,145 @@ 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_subquery_in_key (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+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();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE wrong_strategy_name (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE fail_nonexistant_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);
+
+-- 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);
+
+-- cannot contain whole-row references
+CREATE TABLE fail_wholerow_ref (
+ a int
+) PARTITION BY RANGE ((fail_wholerow_ref));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE fail_point_type_key (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+DROP TABLE check_relkind;
+
+-- prevent a function referenced in partition key from being dropped
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE dependency_matters (
+ a int
+) PARTITION BY RANGE (plusone(a));
+DROP FUNCTION plusone(int);
+DROP TABLE dependency_matters;
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE no_inh_parted (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (no_inh_parted);
+DROP TABLE no_inh_parted;
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE no_inh_con_parted (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-10.patch (26.2K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-10.patch)
download | inline diff:
From 3852d78da0f278d25a2b7481b1219ce69d9fc37a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:20:23 +0900
Subject: [PATCH 2/9] 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 | 146 ++++++++++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 134 ++++++++++++++++++++++++-
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, 366 insertions(+), 24 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..7279151 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_table.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,150 @@ 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 relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table 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(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass the hard way */
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ 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);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ /*
+ * 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, 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 4da297f..067353d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4849,6 +4853,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");
@@ -4873,7 +4878,108 @@ getTables(Archive *fout, int *numTables)
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
- if (fout->remoteVersion >= 90600)
+ if (fout->remoteVersion >= 100000)
+ {
+ PQExpBuffer acl_subquery = createPQExpBuffer();
+ PQExpBuffer racl_subquery = createPQExpBuffer();
+ PQExpBuffer initacl_subquery = createPQExpBuffer();
+ PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+ PQExpBuffer attacl_subquery = createPQExpBuffer();
+ PQExpBuffer attracl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ *
+ * Left join to detect if any privileges are still as-set-at-init, in
+ * which case we won't dump out ACL commands for those.
+ */
+
+ buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+ initracl_subquery, "c.relacl", "c.relowner",
+ "CASE WHEN c.relkind = 'S' THEN 's' ELSE 'r' END::\"char\"",
+ dopt->binary_upgrade);
+
+ buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+ attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+ dopt->binary_upgrade);
+
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "%s AS relacl, %s as rrelacl, "
+ "%s AS initrelacl, %s as initrrelacl, "
+ "c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relrowsecurity, c.relforcerowsecurity, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "c.relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "tc.reloptions AS toast_reloptions, "
+ "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = at.attnum)"
+ "WHERE at.attrelid = c.oid AND ("
+ "%s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL"
+ "))"
+ "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 "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = 0) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ acl_subquery->data,
+ racl_subquery->data,
+ initacl_subquery->data,
+ initracl_subquery->data,
+ username_subquery,
+ attacl_subquery->data,
+ attracl_subquery->data,
+ attinitacl_subquery->data,
+ attinitracl_subquery->data,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_TABLE);
+
+ destroyPQExpBuffer(acl_subquery);
+ destroyPQExpBuffer(racl_subquery);
+ destroyPQExpBuffer(initacl_subquery);
+ destroyPQExpBuffer(initracl_subquery);
+
+ destroyPQExpBuffer(attacl_subquery);
+ destroyPQExpBuffer(attracl_subquery);
+ destroyPQExpBuffer(attinitacl_subquery);
+ destroyPQExpBuffer(attinitracl_subquery);
+ }
+ else if (fout->remoteVersion >= 90600)
{
PQExpBuffer acl_subquery = createPQExpBuffer();
PQExpBuffer racl_subquery = createPQExpBuffer();
@@ -5375,6 +5481,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)
{
@@ -5444,6 +5551,10 @@ 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));
+ if (i_partkeydef == -1 || PQgetisnull(res, i, i_partkeydef))
+ tblinfo[i].partkeydef = NULL;
+ else
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5488,7 +5599,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -5590,7 +5703,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);
@@ -14249,6 +14365,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -14309,6 +14428,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14447,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -14544,6 +14665,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 a60cf95..e4db8db 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 6275a68..e57d78e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
}
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
}
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,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 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,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 c6ed153..dbc05b9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -416,3 +416,29 @@ CREATE TABLE no_inh_con_parted (
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+ a int,
+ b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+ b | integer |
+Partition key: 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 6a2f8f6..56aefb4 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -411,3 +411,16 @@ CREATE TABLE no_inh_con_parted (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
+
+-- 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
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-10.patch (201.0K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-10.patch)
download | inline diff:
From 49a44b23fcddca5884db04bcdc24844415cc478f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 14 Jul 2016 14:38:08 +0900
Subject: [PATCH 3/9] Catalog and DDL for partitions.
pg_class gets new fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 101 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 26 +
doc/src/sgml/ref/create_table.sgml | 92 ++-
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 72 ++-
src/backend/catalog/partition.c | 1702 ++++++++++++++++++++++++++++
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 751 +++++++++++--
src/backend/nodes/copyfuncs.c | 51 +
src/backend/nodes/equalfuncs.c | 45 +
src/backend/nodes/nodeFuncs.c | 6 +
src/backend/nodes/outfuncs.c | 30 +
src/backend/nodes/readfuncs.c | 36 +
src/backend/parser/gram.y | 238 ++++-
src/backend/parser/parse_agg.c | 1 -
src/backend/parser/parse_utilcmd.c | 358 ++++++-
src/backend/utils/cache/relcache.c | 105 ++-
src/include/catalog/heap.h | 1 +
src/include/catalog/partition.h | 58 +
src/include/catalog/pg_class.h | 22 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 53 +-
src/include/parser/kwlist.h | 3 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 228 ++++
src/test/regress/expected/create_table.out | 200 ++++
src/test/regress/sql/alter_table.sql | 193 ++++
src/test/regress/sql/create_table.sql | 155 +++
29 files changed, 4429 insertions(+), 142 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ccc2b6b..1e14776 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..cf8452c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,48 @@ 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 table (which might itself be partitioned)
+ as partition of the target table. The partition bound specification must
+ correspond to the partitioning method and partitioning key of the target
+ table. The table to be attached must have all the columns as the target
+ table and no more; moreover, the column types must also match. Also, it
+ must have all the matching constraints as the target table.
+ That includes both <literal>NOT NULL</literal> and <literal>CHECK</literal>
+ constraints. If some <literal>CHECK</literal> constraint of the table
+ being attached is marked <literal>NO INHERIT</literal>, the command will
+ fail; such constraints must either be dropped or recreated without the
+ <literal>NO INHERIT</literal> mark. Currently <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered.
+ </para>
+
+ <para>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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, but no longer has any
+ ties to the table from which it was detached.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -721,8 +770,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>.
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.
+ To add the table as a new child of a parent table, or as a new partition
+ of a partitioned table, you must own the parent table as well.
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
@@ -938,6 +987,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 or to 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>
@@ -978,6 +1045,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 specified.
+ </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.
@@ -1047,6 +1120,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited.
+ A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+ partitioned table, because all partitions of a table must have the same
+ columns as the partitioning root.
</para>
<para>
@@ -1233,6 +1309,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..f05f212 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,12 @@ 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.
+ </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 +329,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</>, as a 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 893c899..6e01dbb 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | START ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] END ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
<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>] [, ... ] ) ]
@@ -232,6 +247,50 @@ 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.
+ </para>
+
+ <para>
+ The partition bound specification must correspond to the partitioning
+ method and partitioning key of the parent table, and must not overlap
+ with any existing partition of that 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.
+ Defaults and constraints can optionally be specified for each of the
+ inherited columns. One can also specify table constraints, in addition
+ to those inherited from the parent. If a check constraint with the name
+ matching one of the parent's constraint is specified, it is merged with
+ the latter, provided the specified condition is same.
+ </para>
+
+ <para>
+ Rows inserted into a partitioned table will be automatically routed to
+ the correct partition. If no suitable partition exists, an error will
+ occur.
+ </para>
+
+ <para>
+ A partition must have the same column names and types as the table of
+ which it is a partition. Therefore, modifications to the column names
+ or types of the partitioned table will automatically propagate to all
+ children, as will operations such as TRUNCATE which normally affect a
+ table and all of its inheritance children. It is also possible to
+ TRUNCATE a partition individually, just as for an inheritance child.
+ Dropping a partition directly using <literal>DROP TABLE</literal>
+ will fail; it must be <firstterm>detached</> from the parent first.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -1424,7 +1483,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 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 31a7bb4..2b63665 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -810,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -821,6 +822,9 @@ InsertPgClassTuple(Relation pg_class_desc,
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ /* relpartbound is set by updating this tuple, if necessary */
+ nulls[Anum_pg_class_relpartbound - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
/*
@@ -926,6 +930,9 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ /* relispartition is always set by updating this tuple later */
+ new_rel_reltup->relispartition = false;
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
@@ -2468,8 +2475,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
* definition) then interpret addition of a local constraint as a
* legal merge. This allows ALTER ADD CONSTRAINT on parent and
* child tables to be given in either order with same end state.
+ * However if the relation is a partition, all inherited
+ * constraints are always non-local, including those that were
+ * merged.
*/
- if (is_local && !con->conislocal)
+ if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
allow_merge = true;
if (!found || !allow_merge)
@@ -2514,10 +2524,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
tup = heap_copytuple(tup);
con = (Form_pg_constraint) GETSTRUCT(tup);
- if (is_local)
- con->conislocal = true;
+ /*
+ * If rel is a partition, a constraint is forced to be non-local,
+ * after merging with the parent's constraint.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ con->conislocal = false;
+ if (!is_local || con->coninhcount == 0)
+ con->coninhcount++;
+ }
else
- con->coninhcount++;
+ {
+ if (is_local)
+ con->conislocal = true;
+ else
+ con->coninhcount++;
+ }
+
if (is_no_inherit)
{
Assert(is_local);
@@ -3170,3 +3194,43 @@ RemovePartitionKeyByRelId(Oid relid)
ReleaseSysCache(tuple);
heap_close(rel, RowExclusiveLock);
}
+
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+ Relation classRel;
+ Form_pg_class classForm;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(!classForm->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..3ec148b
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1702 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; has
+ * same length as the values array above */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ int index;
+ Datum *datums; /* range bound datums */
+ bool *infinite; /* infinite flag for each datum */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, of which we only store one.
+ */
+typedef struct PartitionRangeInfo
+{
+ PartitionRangeBound **bounds;
+ int nbounds;
+ int *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical)
+ */
+typedef struct BoundCollectionData
+{
+ char strategy; /* list or range bounds? */
+
+ union
+ {
+ struct PartitionListInfo lists;
+ struct PartitionRangeInfo ranges;
+ } bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+ Datum value;
+ int index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums,
+ bool inclusive, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+ PartitionRangeBound *,
+ void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *inhoids,
+ *partoids;
+ Oid *oids;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ PartitionListValue **all_values;
+ int all_values_count;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ PartitionRangeBound **distinct_bounds;
+ int num_distinct_bounds;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ partoids = NIL;
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+
+ /*
+ * It is possible that the pg_class tuple of a partition has not been
+ * updated yet to set its relpartbound field. The only case where
+ * this happens is when we open the parent relation to check using its
+ * partition descriptor that a new partition's bound does not overlap
+ * some existing partition. XXX - perhaps it would be better if we
+ * did not need to use the parent relation desc in that case, because
+ * the following looks kind of ugly.
+ */
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+ {
+ ReleaseSysCache(tuple);
+ continue;
+ }
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ partoids = lappend_oid(partoids, inhrelid);
+ }
+
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc(nparts * sizeof(Oid));
+ i = 0;
+ foreach (cell, partoids)
+ oids[i++] = lfirst_oid(cell);
+
+ /* Convert from node to the internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ List *non_null_values = NIL;
+
+ /*
+ * Create a unified list of non-null values across all
+ * partitions.
+ */
+ i = 0;
+ foreach(cell, boundspecs)
+ {
+ ListCell *c;
+ PartitionBoundSpec *spec = lfirst(cell);
+
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ foreach (c, spec->listdatums)
+ {
+ Const *val = lfirst(c);
+ PartitionListValue *list_value = NULL;
+
+ if (!val->constisnull)
+ {
+ list_value = (PartitionListValue *)
+ palloc0(sizeof(PartitionListValue));
+ list_value->index = i;
+ list_value->value = datumCopy(val->constvalue,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ }
+ else
+ {
+ /*
+ * Never put a null into the values array, flag
+ * instead for the code further down below where
+ * we construct the actual relcache struct.
+ */
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+
+ if (list_value)
+ non_null_values = lappend(non_null_values,
+ list_value);
+ }
+
+ i++;
+ }
+
+ all_values_count = list_length(non_null_values);
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (PartitionListValue **)
+ palloc(all_values_count *
+ sizeof(PartitionListValue *));
+ i = 0;
+ foreach(cell, non_null_values)
+ {
+ PartitionListValue *src = lfirst(cell);
+
+ all_values[i] = (PartitionListValue *)
+ palloc(sizeof(PartitionListValue));
+ all_values[i]->value = datumCopy(src->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+ all_values[i]->index = src->index;
+ i++;
+ }
+
+ qsort_arg(all_values, all_values_count,
+ sizeof(PartitionListValue *),
+ qsort_partition_list_value_cmp,
+ (void *) key);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ int j, k;
+ PartitionRangeBound **all_bounds,
+ *prev;
+
+ all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+ sizeof(PartitionRangeBound *));
+
+ /*
+ * Create a unified list of range bounds across all the
+ * partitions.
+ */
+ i = j = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundSpec *spec = lfirst(cell);
+ PartitionRangeBound *lower, *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, i,
+ spec->lowerdatums,
+ spec->lowerinc, true);
+ /*
+ * In most cases, upper bound of the i'th partition is
+ * equal in *value* to the lower bound of the (i+1)'th
+ * partition, execpt they have different inclusivity.
+ * We want to keep only the former in the final list of
+ * bounds by eliminating the latter. To be able to equate
+ * the two, we flip the inclusivity flag of the latter,
+ * so that it matches the former's flag value in exactly
+ * the cases where the elimination must occur.
+ */
+ lower->inclusive = !lower->inclusive;
+
+ upper = make_one_range_bound(key, i,
+ spec->upperdatums,
+ spec->upperinc, false);
+ all_bounds[j] = lower;
+ all_bounds[j+1] = upper;
+ j += 2;
+ i++;
+ }
+ Assert(j == 2 * nparts);
+
+ /* Sort all the bounds in ascending order */
+ qsort_arg(all_bounds, 2 * nparts,
+ sizeof(PartitionRangeBound *),
+ qsort_partition_rbound_cmp,
+ (void *) key);
+ /*
+ * Count the number of distinct bounds to allocate an array
+ * of that size.
+ */
+ num_distinct_bounds = 0;
+ prev = NULL;
+ for (i = 0; i < j; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ /*
+ * Count the current bound if it is distinct from the
+ * previous one.
+ */
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ num_distinct_bounds++;
+
+ prev = cur;
+ }
+
+ /*
+ * Finally save them in an array from where they will be
+ * copied into the relcache.
+ */
+ distinct_bounds = (PartitionRangeBound **)
+ palloc0(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ k = 0;
+ prev = NULL;
+ for (i = 0; i < j; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ distinct_bounds[k++] = cur;
+
+ /*
+ * If it's a lower bound, flip the inclusive flag to its
+ * original value.
+ */
+ if (cur->lower)
+ cur->inclusive = !cur->inclusive;
+
+ prev = cur;
+ }
+ Assert(k == num_distinct_bounds);
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ int *mapping;
+ int next_index = 0;
+
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->boundinfo = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ result->boundinfo->strategy = key->strategy;
+
+ /* Allocate and initialize with invalid mapping values */
+ mapping = (int *) palloc(sizeof(int) * nparts);
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionListInfo listinfo;
+
+ listinfo.nvalues = all_values_count;
+ listinfo.has_null = found_null_partition;
+ listinfo.values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo.indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they match for any two list partitioned tables
+ * with same number of partitions and same lists per partition.
+ * One way to achieve this is to assign indexes in the same
+ * order as the least values of the individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo.values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo.indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo.null_index = mapping[null_partition_index];
+ else
+ listinfo.null_index = -1;
+
+ result->boundinfo->bounds.lists = listinfo;
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeInfo rangeinfo;
+
+ rangeinfo.bounds = (PartitionRangeBound **)
+ palloc(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ rangeinfo.indexes = (int *)
+ palloc((num_distinct_bounds + 1) *
+ sizeof(int));
+
+ for (i = 0; i < num_distinct_bounds; i++)
+ {
+ rangeinfo.bounds[i] =
+ copy_range_bound(key, distinct_bounds[i]);
+
+ /* There is no mapping for invalid indexes (ie, -1) */
+ if (rangeinfo.bounds[i]->lower)
+ rangeinfo.indexes[i] = -1;
+ else
+ {
+ int orig_index = rangeinfo.bounds[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[orig_index] == -1)
+ mapping[orig_index] = next_index++;
+
+ rangeinfo.indexes[i] = mapping[orig_index];
+ }
+ }
+ rangeinfo.indexes[i] = -1;
+ rangeinfo.nbounds = num_distinct_bounds;
+
+ result->boundinfo->bounds.ranges = rangeinfo;
+ break;
+ }
+ }
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of the
+ * list values or the range bounds (ie, sorted).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+ pfree(mapping);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2)
+{
+ if (b1->strategy != b2->strategy)
+ return false;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key,
+ &b1->bounds.lists, &b2->bounds.lists))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key,
+ &b1->bounds.ranges, &b2->bounds.ranges))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent. Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+ bool overlap = false;
+
+ parent = heap_open(parentId, NoLock); /* already locked */
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ if (pdesc->nparts > 0)
+ {
+ ListCell *cell;
+ bool new_has_null = false;
+ PartitionListInfo listinfo;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+ Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+ /* If the new partition's list contains null, set a flag. */
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ {
+ new_has_null = true;
+ break;
+ }
+ }
+
+ /*
+ * Now check against listinfo whether the new partition's
+ * values are not already taken by existing partitions.
+ */
+ if (new_has_null && listinfo.has_null)
+ {
+ with = listinfo.null_index;
+ overlap = true;
+ }
+ else
+ {
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+ int found = -1;
+
+ /* bsearch a new list's value in listinfo->values */
+ if (!val->constisnull)
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ val->constvalue);
+ if (found >= 0)
+ {
+ overlap = true;
+ with = listinfo.indexes[found];
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeBound *lower,
+ *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, -1,
+ spec->lowerdatums, spec->lowerinc,
+ true);
+ upper = make_one_range_bound(key, -1,
+ spec->upperdatums, spec->upperinc,
+ false);
+
+ /*
+ * First check if the resulting range would be empty with
+ * specified bounds
+ */
+ if (partition_rbound_cmp(key, lower, upper) > 0 ||
+ (partition_rbound_cmp(key, lower, upper) == 0
+ && (!spec->lowerinc || !spec->upperinc)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, spec->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ PartitionRangeInfo rangeinfo;
+ int offset1, offset2;
+ bool equal = false;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ /*
+ * Find the greatest index of a range bound that is less
+ * than the new lower bound.
+ */
+ offset1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, lower,
+ partition_rbound_cmp, false, &equal);
+
+ /*
+ * If equal has been set to true, that means the new bound is
+ * found to be equal to some existing bound. offset1 == -1
+ * means that all existing range bounds are greater than the
+ * new bound. If offset >= 0, and there exists a gap between
+ * the corresponding bound and the next, new partition can
+ * still manage to fit within the same. To confirm whether it
+ * is so, check the new upper bound.
+ */
+ if (!equal &&
+ (offset1 < 0 || rangeinfo.indexes[offset1+1] < 0))
+ {
+ offset2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, upper,
+ partition_rbound_cmp, false,
+ &equal);
+
+ if (equal || offset1 != offset2)
+ {
+ overlap = true;
+ with = rangeinfo.indexes[offset2+1];
+ }
+ }
+ else
+ {
+ overlap = true;
+ if (offset1 == -1)
+ {
+ Assert(equal);
+ offset1 = 0;
+ }
+ with = rangeinfo.indexes[offset1+1];
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (overlap)
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, spec->location)));
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Returns a list of all leaf partitions of relation with OID 'relid'
+ *
+ * All returned OIDs will have been locked by find_inheritance_children().
+ */
+List *
+get_leaf_partition_oids(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ partitions = find_inheritance_children(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+ Relation partRel;
+
+ partRel = heap_open(myoid, NoLock);
+
+ if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ result = list_concat(result,
+ get_leaf_partition_oids(myoid, lockmode));
+ else
+ result = lappend_oid(result, myoid);
+
+ heap_close(partRel, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition check constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual;
+ TupleDesc parent_tupdesc = RelationGetDescr(parent);
+ AttrNumber parent_attno;
+ AttrNumber *partition_attnos;
+ bool found_whole_row;
+
+ Assert(key != NULL);
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, spec);
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, spec);
+ break;
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partition_attnos = (AttrNumber *)
+ palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+ for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+ parent_attno++)
+ {
+ Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+ char *attname = NameStr(attribute->attname);
+ AttrNumber partition_attno;
+
+ if (attribute->attisdropped)
+ continue;
+
+ partition_attno = get_attnum(RelationGetRelid(rel), attname);
+ partition_attnos[parent_attno - 1] = partition_attno;
+ }
+
+ my_qual = (List *) map_variable_attnos((Node *) my_qual,
+ 1, 0,
+ partition_attnos,
+ parent_tupdesc->natts,
+ &found_whole_row);
+ /* there can never be a whole-row reference in there*/
+ Assert(!found_whole_row);
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result;
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If the list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, remove the null Const from the list and create a IS NULL
+ * test and create an OR expression along with ScalarArrayOpExpr. We do
+ * it this way because the null-valued expression does not have the
+ * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(spec->listdatums); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ spec->listdatums = list_delete_cell(spec->listdatums,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = spec->listdatums;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ if (nulltest1)
+ result = list_make2(nulltest1, opexpr);
+ else if (nulltest2)
+ {
+ BoolExpr *or;
+
+ or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+ result = list_make1(or);
+ }
+ else
+ result = list_make1(opexpr);
+
+ return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+
+ /*
+ * Iterate over columns of the key emitting an OpExpr for each, based
+ * on the corresponding elements of the lower and upper datums.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum = lfirst(cell1),
+ *udatum = lfirst(cell2);
+ Node *key_col;
+ Const *lower_val = NULL,
+ *upper_val = NULL;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+ Oid operoid;
+ uint16 strategy;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * If either of lower or upper datum is infinite, stop at this column,
+ * but do emit an OpExpr for the non-infinite datum.
+ */
+ if (!ldatum->infinite)
+ lower_val = (Const *) ldatum->value;
+ if (!udatum->infinite)
+ upper_val = (Const *) udatum->value;
+
+ /*
+ * Is lower_val = upper_val?
+ */
+ if (lower_val && upper_val)
+ {
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /* Yes, build leftop eq lower_val */
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go over to consider the next column. */
+ i++;
+ continue;
+ }
+ }
+
+ if (lower_val)
+ {
+ /* Build leftop ge/gt lower_val */
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ if (upper_val)
+ {
+ /* Build leftop le/lt upper_val */
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ /*
+ * We can stop at this column for we cannot say anything about the
+ * further columns, because they were not checked during tuple-routing
+ * for any row that made into this partition. In other words,
+ * comparing corresponding columns of an input row with this pair of
+ * lower and upper datums would have conclusively determined whether
+ * the row maps to this partition or not.
+ */
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ *
+ * Use either the column type as the operator datatype or opclass's declared
+ * input type.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ if (need_relabel)
+ *need_relabel = false;
+
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Get pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+ Datum value1 = (*(const PartitionListValue **) a)->value,
+ value2 = (*(const PartitionListValue **) b)->value;
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = partition_list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums,
+ bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+ int i;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->index = index;
+ bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ i = 0;
+ foreach (cell, datums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+
+ bound->infinite[i] = datum->infinite;
+
+ if (!bound->infinite[i])
+ {
+ Const *val = (Const *) datum->value;
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->datums[i] = datumCopy(val->constvalue,
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ i++;
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->index = src->index;
+ result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+ result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ for (i = 0; i < partnatts; i++)
+ {
+ result->infinite[i] = src->infinite[i];
+ if (!result->infinite[i])
+ result->datums[i] = datumCopy(src->datums[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+ int i;
+
+ if (r1->nbounds != r2->nbounds)
+ return false;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < r1->nbounds; i++)
+ {
+ if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+ return false;
+
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+ }
+
+ /* There is one more index than bounds */
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+
+ return true;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+ PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int32 cmpval;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ /*
+ * First, handle cases involving infinity, which don't require
+ * invoking the comparison proc.
+ */
+ if (b1->infinite[i] && b2->infinite[i])
+ {
+ /*
+ * 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[i])
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite[i])
+ return b2->lower ? 1 : -1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /*
+ * 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 (cmpval == 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 cmpval;
+}
+
+/*
+ * Are two (consecutive) range bounds equal without distinguishing lower
+ * and upper?
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ int32 cmpval = 0;
+
+ /*
+ * If either of them has infinite element, we can't equate them.
+ * Even if both were infinite, they'd have opposite signs (it always
+ * holds that b1 and b2 are lower and upper bound or vice versa).
+ */
+ if (b1->infinite[i] || b2->infinite[i])
+ return false;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ return false;
+ }
+
+ /*
+ * All datums are found to be equal, now only if both are inclusive
+ * or exclusive then they are considered equal.
+ */
+ if (b1->inclusive == b2->inclusive)
+ return true;
+
+ return false;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+ PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple. The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal)
+{
+ int lo, hi, mid;
+
+ lo = -1;
+ hi = n - 1;
+ while (lo < hi)
+ {
+ PartitionRangeBound *cur;
+ int32 cmpval;
+
+ mid = (lo + hi + 1) / 2;
+ cur = bounds[mid];
+
+ cmpval = cmp(key, cur, probe);
+ if (is_equal)
+ *is_equal = (cmpval == 0);
+
+ if (cmpval < 0 || (equal_allowed && cmpval == 0))
+ lo = mid;
+ else
+ hi = mid - 1;
+ }
+
+ return lo;
+}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..0ad14de 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c2ae3f8..dc28393 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -163,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -267,6 +270,7 @@ struct DropRelationCallbackState
char relkind;
Oid heapOid;
bool concurrent;
+ bool relispartition;
};
/* Alter table target-type flags for ATSimplePermissions */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse);
static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -594,10 +606,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
@@ -717,6 +735,29 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition bound, if any. */
+ 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(relname, parentId, stmt->partbound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(rel, stmt->partbound);
+
+ /*
+ * The following code may also update the pg_class tuple to update
+ * relnumchecks, so bump up the command counter to avoid the "already
+ * updated by self" error.
+ */
+ 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
@@ -938,6 +979,7 @@ RemoveRelations(DropStmt *drop)
state.relkind = relkind;
state.heapOid = InvalidOid;
state.concurrent = drop->concurrent;
+ state.relispartition = false;
relOid = RangeVarGetRelidExtended(rel, lockmode, true,
false,
RangeVarCallbackForDropRelation,
@@ -950,6 +992,13 @@ RemoveRelations(DropStmt *drop)
continue;
}
+ /* Cannot drop a partition directly. */
+ if (state.relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop partition \"%s\"", rel->relname),
+ errdetail("You must detach it from the parent first.")));
+
/* OK, we're ready to delete this one */
obj.classId = RelationRelationId;
obj.objectId = relOid;
@@ -1004,6 +1053,12 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
classform = (Form_pg_class) GETSTRUCT(tuple);
/*
+ * Return whether this relation is actually a partition that must not be
+ * dropped on its own.
+ */
+ state->relispartition = classform->relispartition;
+
+ /*
* Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
* but RemoveRelations() can only pass one relkind for a given relation.
* It chooses RELKIND_RELATION for both regular and partitioned tables.
@@ -1093,7 +1148,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
ListCell *child;
List *children;
@@ -1471,7 +1527,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1516,8 +1573,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1544,6 +1601,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1579,18 +1648,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from table \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1728,6 +1816,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1856,6 +1945,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1885,16 +1977,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1931,8 +2027,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 */
@@ -2452,7 +2549,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -3126,6 +3223,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);
@@ -3237,12 +3339,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepDropNotNull(rel, recurse);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepSetNotNull(rel, recurse);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
@@ -3443,6 +3547,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3513,7 +3623,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, following
+ * check is unnecessary.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3762,6 +3878,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3947,7 +4069,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4027,6 +4149,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4091,6 +4214,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4280,6 +4412,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("child table contains a row violating partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4477,7 +4615,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4799,6 +4938,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5245,6 +5389,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be dropped from child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+}
static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
@@ -5320,6 +5477,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
+ /* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
/*
* Okay, actually perform the catalog change ... if needed
*/
@@ -5352,6 +5526,20 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
* Return the address of the modified column. If the column was already NOT
* NULL, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be added to the child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be added to child tables too")));
+}
+
static ObjectAddress
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
@@ -5912,6 +6100,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
Relation attr_rel;
ListCell *child;
+ /*
+ * In case of a partitioned table, the column must be dropped from the
+ * partitions as well.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column must be dropped from child tables too")));
+
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(child, children)
{
@@ -7911,6 +8108,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
}
/*
+ * In case of a partitioned table, the constraint must be dropped from
+ * the partitions too. There is no such thing as NO INHERIT constraints
+ * in case of partitioned tables.
+ */
+ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+
+ /*
* 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.
@@ -10212,6 +10419,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10224,12 +10436,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;
@@ -10274,37 +10481,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from \"%s\"", parent->relname),
errdetail("Table \"%s\" is partitioned.", parent->relname)));
- /*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10339,6 +10520,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);
@@ -10353,16 +10597,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;
}
/*
@@ -10413,7 +10649,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
@@ -10431,12 +10667,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10476,14 +10716,22 @@ 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 child table must be marked NOT NULL",
+ attributeName)));
/*
* OK, bump the child column's inheritance count. (If we fail
* later on, this change will just roll back.)
*/
childatt->attinhcount++;
+
+ /*
+ * In case of partitions, we must enforce that value of attislocal
+ * is same in all partitions. (Note: there are only inherited
+ * attributes in partitions)
+ */
+ if (is_attach_partition)
+ childatt->attislocal = false;
simple_heap_update(attrrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrrel, tuple);
heap_freetuple(tuple);
@@ -10506,7 +10754,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.
@@ -10525,10 +10773,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10605,6 +10857,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
+ /*
+ * In case of partitions, we must enforce that value of conislocal
+ * is same in all partitions for inherited constraints.
+ */
+ if (is_attach_partition)
+ child_con->conislocal = false;
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10629,6 +10889,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10642,13 +10942,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];
@@ -10657,19 +10955,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10679,7 +10968,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);
@@ -10700,11 +10989,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
@@ -10713,7 +11011,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)))
@@ -10775,7 +11073,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);
@@ -10806,7 +11104,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)
@@ -10818,30 +11116,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;
}
/*
@@ -12524,3 +12812,242 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * 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,
+ inheritsRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that is a inheritance child as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(attachRel, cmd->bound);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel),
+ AccessExclusiveLock);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f283a97..90d89d2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4202,6 +4204,46 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+ PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(listdatums);
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lowerdatums);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upperdatums);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+ PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+ COPY_SCALAR_FIELD(infinite);
+ COPY_NODE_FIELD(value);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5122,6 +5164,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundSpec:
+ retval = _copyPartitionBoundSpec(from);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _copyPartitionRangeDatum(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a6421d2..12f3438 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2376,6 +2377,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2657,6 +2659,40 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(listdatums);
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lowerdatums);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upperdatums);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+ COMPARE_SCALAR_FIELD(infinite);
+ COMPARE_NODE_FIELD(value);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3416,6 +3452,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundSpec:
+ retval = _equalPartitionBoundSpec(a, b);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _equalPartitionRangeDatum(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
+ case T_PartitionBoundSpec:
+ loc = ((const PartitionBoundSpec *) expr)->location;
+ break;
+ case T_PartitionRangeDatum:
+ loc = ((const PartitionRangeDatum *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 417e20a..9e7ae1f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3290,6 +3292,28 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUND");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(listdatums);
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lowerdatums);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+ WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+ WRITE_BOOL_FIELD(infinite);
+ WRITE_NODE_FIELD(value);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3880,6 +3904,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundSpec:
+ _outPartitionBoundSpec(str, obj);
+ break;
+ case T_PartitionRangeDatum:
+ _outPartitionRangeDatum(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..655aa1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,38 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+ READ_LOCALS(PartitionBoundSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_NODE_FIELD(listdatums);
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lowerdatums);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upperdatums);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+ READ_LOCALS(PartitionRangeDatum);
+
+ READ_BOOL_FIELD(infinite);
+ READ_NODE_FIELD(value);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2529,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONBOUND", 14))
+ return_value = _readPartitionBoundSpec();
+ else if (MATCH("PARTRANGEDATUM", 14))
+ return_value = _readPartitionRangeDatum();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d32a20..17468b2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionSpec *partspec;
+ PartitionRangeDatum *partrange_datum;
}
%type <node> stmt schema_stmt
@@ -547,6 +548,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partbound_datum
+%type <list> partbound_datum_list
+%type <boolean> lb_inc ub_inc
+%type <partrange_datum> PartitionRangeDatum
+%type <list> range_datum_list
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -572,7 +582,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
@@ -588,7 +598,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -602,7 +613,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2374,6 +2385,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:
@@ -2469,6 +2512,88 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partbound_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatums = $5;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START '(' range_datum_list ')' lb_inc
+ END_P '(' range_datum_list ')' ub_inc
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ n->lowerinc = $7;
+ n->lowerdatums = $5;
+ n->upperinc = $12;
+ n->upperdatums = $10;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+partbound_datum:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partbound_datum_list:
+ partbound_datum { $$ = list_make1($1); }
+ | partbound_datum_list ',' partbound_datum
+ { $$ = lappend($1, $3); }
+ ;
+
+range_datum_list:
+ PartitionRangeDatum { $$ = list_make1($1); }
+ | range_datum_list ',' PartitionRangeDatum
+ { $$ = lappend($1, $3); }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+PartitionRangeDatum:
+ UNBOUNDED
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = true;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | partbound_datum
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = false;
+ n->value = $1;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
/*****************************************************************************
*
@@ -2886,6 +3011,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2931,6 +3094,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2953,6 +3121,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2964,6 +3143,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2973,6 +3157,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2994,6 +3179,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -4551,6 +4737,48 @@ CreateForeignTableStmt:
n->options = $14;
$$ = (Node *) n;
}
+ | CREATE FOREIGN TABLE qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $4;
+ n->base.inhRelations = list_make1($7);
+ n->base.tableElts = $8;
+ n->base.partbound = (Node *) $9;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = false;
+ /* FDW-specific data */
+ n->servername = $11;
+ n->options = $12;
+ $$ = (Node *) n;
+ }
+ | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $7->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $7;
+ n->base.inhRelations = list_make1($10);
+ n->base.tableElts = $11;
+ n->base.partbound = (Node *) $12;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = true;
+ /* FDW-specific data */
+ n->servername = $14;
+ n->options = $15;
+ $$ = (Node *) n;
+ }
;
/*****************************************************************************
@@ -11156,6 +11384,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13755,6 +13984,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13801,6 +14031,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13843,6 +14074,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9cb9222..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
-
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..c0931b2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -47,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +65,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +92,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -231,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -249,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partspec)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -359,6 +372,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -898,6 +912,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1117,6 +1132,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2596,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2679,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3059,326 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ RangeVar *part = cxt->relation;
+ RangeVar *partof = linitial(cxt->inhRelations);
+ Relation parentRel;
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* Permanent rels cannot be partitions of temporary ones */
+ if (part->relpersistence != RELPERSISTENCE_TEMP &&
+ parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation \"%s\"",
+ partof->relname)));
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (!cxt->hasoids && parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table without OIDs as partition of table with OIDs")));
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification as per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+ *result_spec;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+
+ result_spec = copyObject(spec);
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListCell *cell;
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ result_spec->listdatums = NIL;
+ foreach(cell, spec->listdatums)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+
+ value = (Node *) make_const(cxt->pstate, &con->val, con->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(cxt->pstate,
+ exprLocation((Node *) con))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ result_spec->listdatums = lappend(result_spec->listdatums,
+ value);
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ ListCell *cell1,
+ *cell2;
+ int i,
+ j;
+ char *colname;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+ if (list_length(spec->lowerdatums) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values than the length of the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(spec->lowerdatums,
+ list_length(spec->lowerdatums) - 1)))));
+ else if (list_length(spec->lowerdatums) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values than the length of the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(spec->lowerdatums,
+ list_length(spec->lowerdatums) - 1)))));
+
+ if (list_length(spec->upperdatums) > partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values than the length of the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(spec->upperdatums,
+ list_length(spec->upperdatums) - 1)))));
+ else if (list_length(spec->upperdatums) < partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values than the length of the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(spec->upperdatums,
+ list_length(spec->upperdatums) - 1)))));
+
+ i = j = 0;
+ result_spec->lowerdatums = result_spec->upperdatums = NIL;
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum,
+ *rdatum;
+ Node *value;
+ A_Const *lcon = NULL,
+ *rcon = NULL;
+
+ ldatum = (PartitionRangeDatum *) lfirst(cell1);
+ rdatum = (PartitionRangeDatum *) lfirst(cell2);
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ /*if (ldatum->infinite && rdatum->infinite)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END datum for column \"%s\" cannot be UNBOUNDED",
+ colname),
+ parser_errposition(cxt->pstate, rdatum->location)));*/
+
+ if (!ldatum->infinite)
+ lcon = (A_Const *) ldatum->value;
+ if (!rdatum->infinite)
+ rcon = (A_Const *) rdatum->value;
+
+ if (lcon)
+ {
+ value = (Node *) make_const(cxt->pstate, &lcon->val, lcon->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation((Node *) ldatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ ldatum->value = value;
+ }
+
+ if (rcon)
+ {
+ value = (Node *) make_const(cxt->pstate, &rcon->val, rcon->location);
+ value = coerce_to_target_type(cxt->pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(cxt->pstate, exprLocation((Node *) rdatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ rdatum->value = value;
+ }
+
+ result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+ copyObject(ldatum));
+ result_spec->upperdatums = lappend(result_spec->upperdatums,
+ copyObject(rdatum));
+
+ ++i;
+ }
+
+ break;
+ }
+ }
+
+ return (Node *) result_spec;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e46f879..d88749a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? No mind order - in the list case, it matches the order
+ * in which partition oids are returned by a pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The iteration logic is
+ * local to partition.c.
+ */
+ if (pdesc1->boundinfo != NULL)
+ {
+ if (pdesc2->boundinfo == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key,
+ pdesc1->boundinfo, pdesc2->boundinfo))
+ return false;
+ }
+ else if (pdesc2->boundinfo != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1344,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2352,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2504,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2520,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2551,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2609,13 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ {
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+ SWAPFIELD(MemoryContext, rd_pdcxt);
+ }
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3765,6 +3842,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5290,6 +5381,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
Oid *partopclass,
Oid *partcollation);
extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
#endif /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..8df8454
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
+ *
+ * The internal structure is opaque outside partition.c. Users must pass an
+ * instance of this struct *and* an instance of PartitionKey to perform any
+ * operations with its contents.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection boundinfo; /* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partition_oids(Oid relid, int lockmode);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 65d0009..7d7651c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -455,6 +456,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundSpec,
+ T_PartitionRangeDatum,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8cc41cf..62a189d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -593,6 +593,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -735,6 +736,51 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+ NodeTag type;
+
+ char strategy;
+
+ /* List partition values */
+ List *listdatums;
+
+ /* Range partition lower and upper bound spec*/
+ bool lowerinc;
+ List *lowerdatums;
+ bool upperinc;
+ List *upperdatums;
+
+ int location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+ NodeTag type;
+
+ bool infinite;
+ Node *value;
+
+ int location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1562,7 +1608,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1788,7 +1836,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..ea6a12b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f7c0ab0..fbf88e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -123,6 +123,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 2c913e5..becbca4 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3020,3 +3020,231 @@ ERROR: cannot change inheritance of partitioned table
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+-- ATTACH PARTITION
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES START (1) ...
+ ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not already a child table
+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 that 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 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 that the table being attached has only columns present 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 that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child 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: child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing constraint "check_a"
+-- check that the constraint 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: child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+ERROR: child table contains a row violating partition bound specification
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+ERROR: child table contains a row violating partition bound specification
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check that 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 that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check that the partition being detached is actually 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, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal
+-------------+------------
+ 0 | t
+ 0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal
+-------------+------------
+ 0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR: column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR: column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+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 inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR: constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR: constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR: constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR: constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index dbc05b9..e391f17 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -442,3 +442,203 @@ Table "public.describe_list_key"
Partition key: LIST (a)
DROP TABLE describe_range_key, describe_list_key;
+--
+-- CREATE TABLE PARTITION OF
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES START (1) ...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR: specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START has more values than the length of the partition key
+LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z...
+ ^
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END has more values than the length of the partition key
+LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ ^
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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 fail_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;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES IN ('a') WITHOUT OIDS;
+ERROR: cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+ERROR: partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+ERROR: partition "fail_part" would overlap partition "part2"
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+ERROR: partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+ERROR: cannot drop partition "part_a"
+DETAIL: You must detach it from the parent first.
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_a depends on table parted
+table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 17 other objects
+DETAIL: drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_a
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 3dbeb48..a20cac2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1909,3 +1909,196 @@ ALTER TABLE no_inh_child INHERIT inh_parent;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent;
+
+-- ATTACH PARTITION
+
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not already a child table
+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 that 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 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 that the table being attached has only columns present 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 that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+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 that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 56aefb4..13d9ce2 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -424,3 +424,158 @@ CREATE TABLE describe_list_key (
) PARTITION BY LIST (a);
\d describe_list_key
DROP TABLE describe_range_key, describe_list_key;
+
+--
+-- CREATE TABLE PARTITION OF
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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 fail_part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES IN ('a') WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partitions 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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-10.patch (21.3K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-10.patch)
download | inline diff:
From e002ca35c5a36eb2e448f4d1484cad09a628bd95 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 12 Jul 2016 17:50:33 +0900
Subject: [PATCH 4/9] 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 | 86 +++++++++++++++++++++++
src/bin/pg_dump/common.c | 86 +++++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 105 ++++++++++++++++++++++++++--
src/bin/pg_dump/pg_dump.h | 12 +++
src/bin/psql/describe.c | 85 +++++++++++++++++++---
src/test/regress/expected/create_table.out | 40 +++++++++++
src/test/regress/sql/create_table.sql | 12 +++
7 files changed, 409 insertions(+), 17 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7279151..1c30767 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8411,6 +8411,92 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundSpec:
+ {
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ switch (spec->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->listdatums != NIL);
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->lowerdatums != NIL &&
+ spec->upperdatums != NIL &&
+ list_length(spec->lowerdatums) ==
+ list_length(spec->upperdatums));
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " START");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->lowerdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ if (!spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+
+ appendStringInfoString(buf, " END");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->upperdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ if (spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ break;
+
+ default:
+ elog(ERROR, "unrecognized partition strategy: %d",
+ (int) spec->strategy);
+ break;
+ }
+ }
+ 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 067353d..e2726af 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5733,6 +5733,70 @@ 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;
+
+ /* Before version 10, there are no partitions */
+ if (fout->remoteVersion < 100000)
+ {
+ *numPartitions = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -14223,6 +14287,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 */
@@ -14251,8 +14326,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;
@@ -14281,7 +14359,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");
}
@@ -14339,15 +14418,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 (");
@@ -14517,6 +14603,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 e4db8db..4973cd1 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;
@@ -625,6 +636,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 e57d78e..cae1b45 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2606,24 +2652,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index e391f17..23fc457 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -611,6 +611,46 @@ 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 level-2 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 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+ "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+ Table "public.part_c"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 0
+Partition key: LIST (a)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
ERROR: cannot drop partition "part_a"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 13d9ce2..cdfa783 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -572,6 +572,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
-- create a level-2 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Refactor-optimizer-s-inheritance-set-expansion-code-10.patch (16.2K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/6-0005-Refactor-optimizer-s-inheritance-set-expansion-code-10.patch)
download | inline diff:
From 32a42cb2b6636474b0772816150bba8681c0b41b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 25 Aug 2016 17:49:59 +0900
Subject: [PATCH 5/9] Refactor optimizer's inheritance set expansion code.
Currently, a inheritance set is flattened upon expansion so that
AppendRelInfos so formed do not preserve the immediate parent-child
relationship which could be useful information in certain optimization
scenarios. That is especially true for partitioned tables which are
fashioned as inheritance hierarchies.
Because certain restrictions (such as multiple inheritance) that prevent
regular inheritance expansion to be done recursively do not hold for
partitioned table hierarchies, do the partitioned table inheritance set
expansion recursively.
Consider this fact (non-flattened inheritance set) in places such as
create_lateral_join_info() that traverse append_rel_list to propagate
certain query transformations from the parent to child tables.
If relation is the target table (UPDATE and DELETE), flattening is
done regardless (scared to modify inheritance_planner() yet).
---
src/backend/optimizer/plan/initsplan.c | 17 ++-
src/backend/optimizer/prep/prepunion.c | 282 +++++++++++++++++++++++---------
src/backend/optimizer/util/plancat.c | 9 +-
3 files changed, 224 insertions(+), 84 deletions(-)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ce6b3..61f3886 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -14,6 +14,7 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -623,8 +624,22 @@ create_lateral_join_info(PlannerInfo *root)
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
+ RangeTblEntry *rte = root->simple_rte_array[rti];
- if (brel == NULL || brel->reloptkind != RELOPT_BASEREL)
+ if (brel == NULL)
+ continue;
+
+ /*
+ * If an "other rel" RTE is a "partitioned table", we must propagate
+ * the lateral info inherited all the way from the root parent to its
+ * children. That's because the children are not linked directly with
+ * the root parent via AppendRelInfo's unlike in case of a regular
+ * inheritance set (see expand_inherited_rtentry()). Failing to
+ * do this would result in those children not getting marked with the
+ * appropriate lateral info.
+ */
+ if (brel->reloptkind != RELOPT_BASEREL &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
continue;
if (root->simple_rte_array[rti]->inh)
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b714783..8f5d8ee 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -111,6 +111,14 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static List *expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten);
+static AppendRelInfo *process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex);
/*
@@ -1324,7 +1332,10 @@ expand_inherited_tables(PlannerInfo *root)
/*
* expand_inherited_rtentry may add RTEs to parse->rtable; there is no
- * need to scan them since they can't have inh=true. So just scan as far
+ * need to scan them here since they can't normally have inh=true. If
+ * the inheritance set represents a partitioned table, some newly added
+ * RTEs will break the above rule if they are partitioned tables
+ * themselves, but they are expanded recursively. So just scan as far
* as the original end of the rtable list.
*/
nrtes = list_length(root->parse->rtable);
@@ -1347,9 +1358,11 @@ expand_inherited_tables(PlannerInfo *root)
* "inh" flag to prevent later code from looking for AppendRelInfos.
*
* Note that the original RTE is considered to represent the whole
- * inheritance set. The first of the generated RTEs is an RTE for the same
- * table, but with inh = false, to represent the parent table in its role
- * as a simple member of the inheritance set.
+ * inheritance set. If the RTE represents a partitioned table, inheritance
+ * set is expanded recursively. The first of the generated RTEs is an RTE
+ * for the same table, but with inh = false, to represent the parent table
+ * in its role as a simple member of the inheritance set. The same applies
+ * to each individual inheritance set in the recursive expansion case.
*
* A childless table is never considered to be an inheritance set; therefore
* a parent RTE must always have at least two associated AppendRelInfos.
@@ -1360,11 +1373,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
Query *parse = root->parse;
Oid parentOID;
PlanRowMark *oldrc;
- Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
List *appinfos;
- ListCell *l;
/* Does RT entry allow inheritance? */
if (!rte->inh)
@@ -1405,19 +1415,69 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /*
+ * Do not flatten the inheritance hierarchy if partitioned table, unless
+ * this is the result relation.
+ */
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE &&
+ rti != root->parse->resultRelation)
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, false);
+ else
+ appinfos = expand_inherited_rte_internal(root, rte, rti, oldrc,
+ lockmode, true);
+
+ /* Add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
+ * expand_inherited_rte_internal
+ * Expand an inheritance set in either non-recursive (flatten=true) or
+ * recursive (flatten=false) manner.
+ *
+ * A inheritance hierarchy is not flttened if it represents a partitioned
+ * table. This allows later planning steps to apply any partitioning
+ * related optimizations in suitable manner.
+ */
+static List *
+expand_inherited_rte_internal(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti, PlanRowMark *oldrc,
+ LOCKMODE lockmode, bool flatten)
+{
+ Oid parentOID;
+ Relation oldrelation;
+ List *inhOIDs;
+ List *appinfos = NIL;
+ ListCell *l;
+ bool has_descendents;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ parentOID = rte->relid;
/*
- * Check that there's at least one descendant, else treat as no-child
+ * Get the list of inheritors.
+ *
+ * Also check that there's at least one descendant, else treat as no-child
* case. This could happen despite above has_subclass() check, if table
* once had a child but no longer does.
*/
- if (list_length(inhOIDs) < 2)
+ if (flatten)
+ {
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ has_descendents = list_length(inhOIDs) >= 2;
+ }
+ else
+ {
+ inhOIDs = find_inheritance_children(parentOID, lockmode);
+ has_descendents = list_length(inhOIDs) >= 1;
+ }
+
+ if (!has_descendents)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
/*
@@ -1434,15 +1494,24 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
*/
oldrelation = heap_open(parentOID, NoLock);
+ /*
+ * Process parent relation in its role as inheritance set member; remember
+ * that parent table OID is not in inhOIDs if we did not flatten the
+ * inheritance tree.
+ */
+ if (!flatten)
+ appinfos = list_make1(process_one_child_table(root, rte, rti,
+ oldrelation, oldrelation,
+ oldrc, false,
+ NULL, NULL));
+
/* Scan the inheritance set and expand it */
- appinfos = NIL;
foreach(l, inhOIDs)
{
Oid childOID = lfirst_oid(l);
Relation newrelation;
RangeTblEntry *childrte;
Index childRTindex;
- AppendRelInfo *appinfo;
/* Open rel if needed; we already have required locks */
if (childOID != parentOID)
@@ -1463,75 +1532,29 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
- * Build an RTE for the child, and attach to query's rangetable list.
- * We copy most fields of the parent's RTE, but replace relation OID
- * and relkind, and set inh = false. Also, set requiredPerms to zero
- * since all required permissions checks are done on the original RTE.
- */
- childrte = copyObject(rte);
- childrte->relid = childOID;
- childrte->relkind = newrelation->rd_rel->relkind;
- childrte->inh = false;
- childrte->requiredPerms = 0;
- parse->rtable = lappend(parse->rtable, childrte);
- childRTindex = list_length(parse->rtable);
-
- /*
- * Build an AppendRelInfo for this parent and child.
- */
- appinfo = makeNode(AppendRelInfo);
- appinfo->parent_relid = rti;
- appinfo->child_relid = childRTindex;
- appinfo->parent_reltype = oldrelation->rd_rel->reltype;
- appinfo->child_reltype = newrelation->rd_rel->reltype;
- make_inh_translation_list(oldrelation, newrelation, childRTindex,
- &appinfo->translated_vars);
- appinfo->parent_reloid = parentOID;
- appinfos = lappend(appinfos, appinfo);
-
- /*
- * Translate the column permissions bitmaps to the child's attnums (we
- * have to build the translated_vars list before we can do this). But
- * if this is the parent table, leave copyObject's result alone.
+ * process_one_child_table() performs the following actions for the
+ * child table:
*
- * Note: we need to do this even though the executor won't run any
- * permissions checks on the child RTE. The insertedCols/updatedCols
- * bitmaps may be examined for trigger-firing purposes.
- */
- if (childOID != parentOID)
- {
- childrte->selectedCols = translate_col_privs(rte->selectedCols,
- appinfo->translated_vars);
- childrte->insertedCols = translate_col_privs(rte->insertedCols,
- appinfo->translated_vars);
- childrte->updatedCols = translate_col_privs(rte->updatedCols,
- appinfo->translated_vars);
- }
-
- /*
- * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ * 1. add a new RTE to the query rtable,
+ * 2. builds a PlanRowMark and adds to the root->rowMarks list
+ * 3. builds and returns AppendRelInfo for parent-child pair
*/
- if (oldrc)
+ appinfos = lappend(appinfos,
+ process_one_child_table(root, rte, rti,
+ oldrelation, newrelation,
+ oldrc, false,
+ &childrte, &childRTindex));
+
+ /* Recurse if we did not flatten the inheritance tree */
+ if (!flatten && has_subclass(childOID))
{
- PlanRowMark *newrc = makeNode(PlanRowMark);
-
- newrc->rti = childRTindex;
- newrc->prti = rti;
- newrc->rowmarkId = oldrc->rowmarkId;
- /* Reselect rowmark type, because relkind might not match parent */
- newrc->markType = select_rowmark_type(childrte, oldrc->strength);
- newrc->allMarkTypes = (1 << newrc->markType);
- newrc->strength = oldrc->strength;
- newrc->waitPolicy = oldrc->waitPolicy;
- newrc->isParent = false;
-
- /* Include child's rowmark type in parent's allMarkTypes */
- oldrc->allMarkTypes |= newrc->allMarkTypes;
-
- root->rowMarks = lappend(root->rowMarks, newrc);
+ Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);
+ childrte->inh = true;
+ appinfos = list_concat(appinfos,
+ expand_inherited_rte_internal(root, childrte,
+ childRTindex, oldrc, lockmode, flatten));
}
- /* Close child relations, but keep locks */
if (childOID != parentOID)
heap_close(newrelation, NoLock);
}
@@ -1547,11 +1570,108 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
{
/* Clear flag before returning */
rte->inh = false;
- return;
+ return NIL;
}
+ return appinfos;
+}
- /* Otherwise, OK to add to root->append_rel_list */
- root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+/*
+ * process_one_child_table
+ * Process one child table in context of inheritance expansion for a
+ * query
+ *
+ * *childRTE & *childRTindex are output variables when non-NULL.
+ */
+static AppendRelInfo *
+process_one_child_table(PlannerInfo *root,
+ RangeTblEntry *parentRTE, Index parentRTindex,
+ Relation parentrel, Relation childrel,
+ PlanRowMark *parent_rc, bool inh,
+ RangeTblEntry **childRTE, Index *childRTindex)
+{
+ Query *parse = root->parse;
+ Oid parentOID = RelationGetRelid(parentrel),
+ childOID = RelationGetRelid(childrel);
+ RangeTblEntry *newrte;
+ Index newrti;
+ AppendRelInfo *appinfo;
+
+ /*
+ * Build an RTE for the child, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, and set inh as requested. Also, set requiredPerms to
+ * zero since all required permissions checks are done on the original
+ * RTE.
+ */
+ newrte = copyObject(parentRTE);
+ newrte->relid = RelationGetRelid(childrel);
+ newrte->relkind = childrel->rd_rel->relkind;
+ newrte->inh = inh;
+ newrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, newrte);
+ newrti = list_length(parse->rtable);
+
+ /* Return the child table RT entry and index if requested */
+ if (childRTE)
+ *childRTE = newrte;
+ if (childRTindex)
+ *childRTindex = newrti;
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = parentRTindex;
+ appinfo->child_relid = newrti;
+ appinfo->parent_reltype = parentrel->rd_rel->reltype;
+ appinfo->child_reltype = childrel->rd_rel->reltype;
+ make_inh_translation_list(parentrel, childrel, newrti,
+ &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+
+ /*
+ * Translate the column permissions bitmaps to the child's attnums (we
+ * have to build the translated_vars list before we can do this). But
+ * if this is the parent table, leave copyObject's result alone.
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ if (childOID != parentOID)
+ {
+ newrte->selectedCols = translate_col_privs(parentRTE->selectedCols,
+ appinfo->translated_vars);
+ newrte->insertedCols = translate_col_privs(parentRTE->insertedCols,
+ appinfo->translated_vars);
+ newrte->updatedCols = translate_col_privs(parentRTE->updatedCols,
+ appinfo->translated_vars);
+ }
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (parent_rc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = newrti;
+ newrc->prti = parentRTindex;
+ newrc->rowmarkId = parent_rc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(newrte, parent_rc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = parent_rc->strength;
+ newrc->waitPolicy = parent_rc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include child's rowmark type in parent's allMarkTypes */
+ parent_rc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ return appinfo;
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5d18206..8ecc116 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1287,8 +1287,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
- /* Only plain relations have constraints */
- if (rte->rtekind != RTE_RELATION || rte->inh)
+ /*
+ * Only plain relations have constraints. We represent a partitioned
+ * table append member as its own append relation and hence would have
+ * set rte->inh in that case.
+ */
+ if (rte->rtekind != RTE_RELATION ||
+ (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
return false;
/*
--
1.7.1
[text/x-diff] 0006-Teach-a-few-places-to-use-partition-check-quals-10.patch (30.2K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/7-0006-Teach-a-few-places-to-use-partition-check-quals-10.patch)
download | inline diff:
From 7fae6d8cd6f3a29920457f5de89c493b21d74093 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:00:09 +0900
Subject: [PATCH 6/9] Teach a few places to use partition check quals.
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/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0ef590a..77d4dcb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2503,7 +2503,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 9773272..714b49c 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,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)
@@ -922,7 +922,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 8ecc116..8036d3f 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 = RelationGetPartitionQual(relation, false);
+ 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 f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index df7cba6..8166f72 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 70107b5..89d5760 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 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/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..72c19e8 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0007-Introduce-a-PartitionTreeNode-data-structure-10.patch (8.0K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/8-0007-Introduce-a-PartitionTreeNode-data-structure-10.patch)
download | inline diff:
From 5fa74490588a22bb4393cdca02b4d1f8380d199f Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 15:47:39 +0900
Subject: [PATCH 7/9] 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 | 208 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 213 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3ec148b..fab418a 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
int index;
} PartitionListValue;
+/*
+ * 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_parts 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;
+
static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
static List *generate_partition_qual(Relation rel, bool recurse);
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -924,6 +982,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1340,6 +1445,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 8df8454..e8da0fa 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -55,4 +56,8 @@ extern Oid get_partition_parent(Oid relid);
extern List *get_leaf_partition_oids(Oid relid, int lockmode);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0008-Tuple-routing-for-partitioned-tables-10.patch (43.8K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/9-0008-Tuple-routing-for-partitioned-tables-10.patch)
download | inline diff:
From 468f996b86f0123fe7a2b5d09cfe1b7333031c10 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 27 Jul 2016 16:59:21 +0900
Subject: [PATCH 8/9] Tuple routing for partitioned tables.
Both COPY FROM and INSERT.
---
src/backend/catalog/partition.c | 327 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 +++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 +++++-
src/backend/executor/nodeModifyTable.c | 142 +++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 71 +++++++
src/backend/optimizer/util/plancat.c | 20 ++-
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 59 ++++++-
src/test/regress/sql/insert.sql | 28 +++
18 files changed, 926 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index fab418a..e80e27f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -195,6 +207,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
static bool equal_range_info(PartitionKey key,
PartitionRangeInfo *r1, PartitionRangeInfo *r2);
static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg);
static bool partition_rbound_eq(PartitionKey key,
PartitionRangeBound *b1, PartitionRangeBound *b2);
typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1464,7 +1478,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;
@@ -1548,6 +1562,284 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ PartitionListInfo listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+
+ if (isnull && listinfo.has_null)
+ return listinfo.null_index;
+ else if (!isnull)
+ {
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ value);
+ if (found >= 0)
+ return listinfo.indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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)
+{
+ int offset;
+ PartitionRangeInfo rangeinfo;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ offset = partition_rbound_bsearch(key,
+ rangeinfo.bounds, rangeinfo.nbounds,
+ tuple, partition_rbound_datum_cmp,
+ true, NULL);
+
+ /*
+ * Offset returned is such that the bound at offset is found to be
+ * less or equal with the tuple. That is, the tuple belongs to the
+ * partition with the rangeinfo.bounds[offset] as the lower bound and
+ * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+ * is indeed marked !lower (that is, it's an upper bound). If it turns
+ * out that it is a lower bound then the corresponding index will be -1,
+ * which means no valid partition exists.
+ */
+ return rangeinfo.indexes[offset+1];
+}
+
/* List partition related support functions */
/*
@@ -1815,6 +2107,39 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
}
/*
+ * Return whether the passed in range bound <=, =, >= tuple specified in arg
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg)
+{
+ Datum *datums1 = bound->datums,
+ *datums2 = (Datum *) arg;
+ int i;
+ int32 cmpval;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ if (bound->infinite[i])
+ return bound->lower ? -1 : 1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ if (cmpval == 0 && !bound->inclusive)
+ return bound->lower ? 1 : -1;
+
+ return cmpval;
+}
+
+/*
* Are two (consecutive) range bounds equal without distinguishing lower
* and upper?
*/
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 77d4dcb..91e4d12 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.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1705,6 +1801,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);
}
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2275,7 +2389,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2383,6 +2498,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2410,6 +2526,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;
@@ -2431,10 +2548,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 */
@@ -2486,6 +2639,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 */
@@ -2506,7 +2684,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)
@@ -2536,7 +2723,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),
@@ -2556,6 +2744,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2569,7 +2763,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 dc28393..7f34227 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1269,6 +1269,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 714b49c..e2853a2 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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 +3002,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 a612b08..d0a5306 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -272,6 +273,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;
/*
@@ -511,6 +537,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
@@ -1565,6 +1597,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)));
@@ -1655,6 +1688,98 @@ 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_TABLE)
+ {
+ 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)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ 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.
*/
@@ -1972,6 +2097,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 90d89d2..ec12ccb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9e7ae1f..a261c92 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 655aa1c..d047160 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..1fef64d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6159,6 +6159,77 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = get_leaf_partition_oids(rte->relid, NoLock);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 8036d3f..f8bfa4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1214,7 +1214,12 @@ get_relation_constraints(PlannerInfo *root,
}
}
- /* Append partition predicates, if any */
+ /*
+ * Append partition predicates, if any. Note that we request the
+ * parent's quals *not* to be included (by passing false) because if the
+ * parent's quals cause it to be excluded, this relation will not be
+ * processed in the first place.
+ */
pcqual = RelationGetPartitionQual(relation, false);
if (pcqual)
{
@@ -1708,3 +1713,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 e8da0fa..165f24a 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/rel.h"
@@ -60,4 +62,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 136276b..c62946f 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 ff8b66b..ce01008 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"
@@ -1147,6 +1148,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 89d5760..0f83bc1 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_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
+ part_nulls | | 1
+(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 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
[text/x-diff] 0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-10.patch (24.7K, ../../9ac4ba01-35cf-658b-5a57-c516c4ccedff@lab.ntt.co.jp/10-0009-Update-DDL-Partitioning-chapter-to-reflect-new-devel-10.patch)
download | inline diff:
From 1a12495e49c35b99d07f9a5a7b5997a3c8948916 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 28 Jul 2016 13:40:02 +0900
Subject: [PATCH 9/9] 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 157512c..fe76ab0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,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</>.
@@ -2793,12 +2793,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>
@@ -2842,59 +2845,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 >= 100 AND outletID < 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>
@@ -2911,8 +2877,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>
@@ -2940,7 +2908,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
@@ -2971,12 +2939,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,
@@ -2986,36 +2954,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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3024,110 +2965,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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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">
@@ -3145,22 +2995,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
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
were created above:
<programlisting>
-CREATE TABLE measurement_y2008m02 (
- CHECK ( logdate >= DATE '2008-02-01' AND logdate < 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
@@ -3186,13 +3029,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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
<programlisting>
SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= DATE '2017-01-01';
</programlisting>
Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate >= 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>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
</programlisting>
Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3397,9 +3173,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>
@@ -3423,18 +3199,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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 16:57 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-01 16:57 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> 4. I'm somewhat wondering if we ought to just legislate that the lower
>> bound is always inclusive and the upper bound is always exclusive.
>> The decision to support both inclusive and exclusive partition bounds
>> is responsible for an enormous truckload of complexity in this patch,
>> and I have a feeling it is also going to be a not-infrequent cause of
>> user error.
>
> I thought we decided at some point to go with range type like notation to
> specify range partition bound because of its flexibility. I agree though
> that with that flexibility, there will more input combinations that will
> cause error. As for the internal complexity, it's not clear to me whether
> it will be reduced by always-inclusive lower and always-exclusive upper
> bounds. We would still need to store the inclusive flag with individual
> PartitionRangeBound and consider it when comparing them with each other
> and with partition key of tuples.
We did. But I now think that was kind of silly. I mean, we also
talked about not having a hard distinction between list partitioning
and range partitioning, but you didn't implement it that way and I
think that's probably a good thing. The question is - what's the
benefit of allowing this to be configurable?
For integers, there's absolutely no difference in expressive power.
If you want to allow 1000-2000 with both bounds inclusive, you can
just say START (1000) END (2001) instead of START (1000) END (2000)
INCLUSIVE. This is also true for any other datatype where it makes
sense to talk about "the next value" and "the previous value".
Instead of making the upper bound inclusive, you can just end at the
next value instead. If you were tempted to make the lower bound
exclusive, same thing.
For strings and numeric types that are not integers, there is in
theory a loss of power. If you want a partition that allows very
value starting with 'a' plus the string 'b' but not anything after
that, you are out of luck. START ('a') END ('b') INCLUSIVE would have
done exactly what you want, but now you need to store the first string
that you *don't* want to include in that partition, and what's that?
Dunno. Or similarly if you want to store everything from 1.0 up to
and including 2.0 but nothing higher, you can't, really.
But who wants that? People who are doing prefix-based partitioning of
their text keys are going to want all of the 'a' things together, and
all of the 'b' things in another category. Same for ranges of
floating-point numbers, which are also probably an unlikely candidate
for a partitioning key anyway.
So let's look at the other side. What do we gain by excluding this
functionality? Well, we save a parser keyword: INCLUSIVE no longer
needs to be a keyword. We also can save some code in
make_one_range_bound(), RelationBuildPartitionDesc(),
copy_range_bound(), partition_rbound_cmp(), and partition_rbound_eq().
Also, if we exclude this now as I'm proposing, we can always add it
back later if it turns out that people need it. On the other hand, if
we include it in the first version, it's going to be very hard to get
rid of it if it turns out we don't want it. Once we release support
for a syntax, we're kinda stuck with it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 17:34 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-01 17:34 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> [ new patches ]
Reviewing 0006:
This patch seems scary. I sort of assumed from the title -- "Teach a
few places to use partition check quals." -- that this was an optional
thing, some kind of optimization from which we could reap further
advantage once the basic infrastructure was in place. But it's not
that at all. It's absolutely necessary that we do this, or data
integrity is fundamentally compromised. How do we know that we've
found all of the places that need to be taught about these new,
uncatalogued constraints?
I'm feeling fairly strongly like you should rewind and make the
partitioning constraints normal catalogued constraints. That's got a
number of advantages, most notably that we can be sure they will be
properly enforced by the entire system (modulo existing bugs, of
course). Also, they'll show up automatically in tools like psql's \d
output, pgAdmin, and anything else that is accustomed to being able to
find constraints in the catalog. We do need to make sure that those
constraints can't be dropped (or altered?) inappropriately, but that's
a relatively small problem. If we stick with the design you've got
here, every client tool in the world needs to be updated, and I'm not
seeing nearly enough advantage in this system to justify that kind of
upheaval.
In fact, as far as I can see, the only advantage of this approach is
that when the insert arrives through the parent and is routed to the
child by whatever tuple-routing code we end up with (I guess that's
what 0008 does), we get to skip checking the constraint, saving CPU
cycles. That's probably an important optimization, but I don't think
that putting the partitioning constraint in the catalog in any way
rules out the possibility of performing that optimization. It's just
that instead of having the partitioning excluded-by-default and then
sometimes choosing to include it, you'll have it included-by-default
and then sometimes choose to exclude it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 17:44 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-01 17:44 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Insisting that you can't drop a child without detaching it first seems
>> wrong to me. If I already made this comment and you responded to it,
>> please point me back to whatever you said. However, my feeling is
>> this is flat wrong and absolutely must be changed.
>
> I said the following [1]:
>
> | Hmm, I don't think I like this. Why should it be necessary to detach
> | a partition before dropping it? That seems like an unnecessary step.
>
> I thought we had better lock the parent table when removing one of its
> partitions and it seemed a bit odd to lock the parent table when dropping
> a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
> PARTITION, the parent table is locked anyway.
That "OTOH" part seems like a pretty relevant point.
Basically, I think people expect to be able to say "DROP THINGTYPE
thingname" or at most "DROP THINGTYPE thingname CASCADE" and have that
thing go away. I'm opposed to anything which requires some other
series of steps without a very good reason, and possible surprise
about the precise locks that the command requires isn't a good enough
reason from my point of view.
>> I wonder if it's really a good idea for the partition constraints to
>> be implicit; what is the benefit of leaving those uncatalogued?
>
> I did start out that way - ie, catalogued implicit constraints, but later
> thought it might not be good to end up with multiple copies of essentially
> the same information. With cataloguing them will come dependencies and
> all places that know about pg_constraint.
>
> In the long term, I think we're only going to need them because we want to
> enforce them when directly inserting data into partitions.
See my other email on this topic. I agree there are some complexities
here, including making sure that pg_dump does the right thing. But I
think it's the right way to go.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 17:49 Corey Huinker <corey.huinker@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 329+ messages in thread
From: Corey Huinker @ 2016-11-01 17:49 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 12:57 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> For strings and numeric types that are not integers, there is in
> theory a loss of power. If you want a partition that allows very
> value starting with 'a' plus the string 'b' but not anything after
> that, you are out of luck. START ('a') END ('b') INCLUSIVE would have
> done exactly what you want, but now you need to store the first string
> that you *don't* want to include in that partition, and what's that?
> Dunno. Or similarly if you want to store everything from 1.0 up to
> and including 2.0 but nothing higher, you can't, really.
>
>
Exactly. This is especially true for date ranges. There's a lot of
cognitive dissonance in defining the "2014" partition as < '2015-01-01', as
was the case in Oracle waterfall-style partitioning. That was my reasoning
for pushing for range-ish syntax as well as form.
> But who wants that? People who are doing prefix-based partitioning of
> their text keys are going to want all of the 'a' things together, and
> all of the 'b' things in another category. Same for ranges of
> floating-point numbers, which are also probably an unlikely candidate
> for a partitioning key anyway.
>
/me raises hand. We have tables with a taxonomy in them where the even
data splits don't fall on single letter boundaries, and often the single
string values have more rows than entire letters. In those situations,
being able to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let
'XYZ1' bleed into the partition and ['XYZ','XYZ1') lets in other values,
and so I go chasing down the non-discrete set rabbit hole.
If we're worried about keywords, maybe a BOUNDED '[]' clause?
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 17:53 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-01 17:53 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> [ new patches ]
Reviewing 0005:
Your proposed commit messages says this:
> If relation is the target table (UPDATE and DELETE), flattening is
> done regardless (scared to modify inheritance_planner() yet).
In the immortal words of Frank Herbert: “I must not fear. Fear is the
mind-killer. Fear is the little-death that brings total obliteration.
I will face my fear. I will permit it to pass over me and through me.
And when it has gone past I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain.”
In other words, I'm not going to accept fear as a justification for
randomly-excluding the target-table case from this code. If there's
an actual reason not to do this in that case or some other case, then
let's document that reason. But weird warts in the code that are
justified only by nameless anxiety are not good.
Of course, the prior question is whether we should EVER be doing this.
I realize that something like this MAY be needed for partition-wise
join, but the mission of this patch is not to implement partition-wise
join. Does anything in this patch series really require this? If so,
what? If not, how about we leave it out and refactor it when that
change is really needed for something?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:01 Robert Haas <robertmhaas@gmail.com>
parent: Corey Huinker <corey.huinker@gmail.com>
1 sibling, 2 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-01 18:01 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 1:49 PM, Corey Huinker <corey.huinker@gmail.com> wrote:
> Exactly. This is especially true for date ranges. There's a lot of cognitive
> dissonance in defining the "2014" partition as < '2015-01-01', as was the
> case in Oracle waterfall-style partitioning. That was my reasoning for
> pushing for range-ish syntax as well as form.
Yeah. That syntax has some big advantages, though. If we define that
partition as START ('2014-01-01') INCLUSIVE END ('2014-12-31')
INCLUSIVE, there's no way for the system to tell that the there's no
gap between the that ending bound and the starting bound of the 2015
partition, because the system has no domain-specific knowledge that
there is no daylight between 2014-12-31 and 2015-01-01. So if we
allow things to be specified that way, then people will use that
syntax and then complain when it doesn't perform quite as well as
START ('2014-01-01') END ('2015-01-01'). Maybe the difference isn't
material and maybe we don't care; what do you think?
(I really don't want to get tied up adding a system for adding and
subtracting one to and from arbitrary data types. Life is too short.
If that requires that users cope with a bit of cognitive dissidence,
well, it's not the first time something like that will have happened.
I have some cognitive dissidence about the fact that creat(2) has no
trailing "e" but truncate(2) does, and moreover the latter can be used
to make a file longer rather than shorter. But, hey, that's what you
get for choosing a career in computer science.)
>> But who wants that? People who are doing prefix-based partitioning of
>> their text keys are going to want all of the 'a' things together, and
>> all of the 'b' things in another category. Same for ranges of
>> floating-point numbers, which are also probably an unlikely candidate
>> for a partitioning key anyway.
>
> /me raises hand. We have tables with a taxonomy in them where the even data
> splits don't fall on single letter boundaries, and often the single string
> values have more rows than entire letters. In those situations, being able
> to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let 'XYZ1' bleed
> into the partition and ['XYZ','XYZ1') lets in other values, and so I go
> chasing down the non-discrete set rabbit hole.
Hmm. I have to admit that I hadn't considered the case where you have
a range partitioning scheme but one of the ranges includes only a
single string. If that's an important use case, that might be a fatal
problem with my proposal. :-(
> If we're worried about keywords, maybe a BOUNDED '[]' clause?
In the end, keywords are not the defining issue here; the issue is
whether all of this complexity around inclusive and exclusive bounds
carries its weight, and whether we want to be committed to that.
Any other opinions out there?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:05 Francisco Olarte <folarte@peoplecall.com>
parent: Corey Huinker <corey.huinker@gmail.com>
1 sibling, 2 replies; 329+ messages in thread
From: Francisco Olarte @ 2016-11-01 18:05 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 6:49 PM, Corey Huinker <corey.huinker@gmail.com> wrote:
>
> On Tue, Nov 1, 2016 at 12:57 PM, Robert Haas <robertmhaas@gmail.com> wrote:
>> For strings and numeric types that are not integers, there is in
>> theory a loss of power. If you want a partition that allows very
>> value starting with 'a' plus the string 'b' but not anything after
>> that, you are out of luck. START ('a') END ('b') INCLUSIVE would have
>> done exactly what you want, but now you need to store the first string
>> that you *don't* want to include in that partition, and what's that?
>> Dunno. Or similarly if you want to store everything from 1.0 up to
>> and including 2.0 but nothing higher, you can't, really.
> Exactly. This is especially true for date ranges. There's a lot of cognitive
> dissonance in defining the "2014" partition as < '2015-01-01', as was the
> case in Oracle waterfall-style partitioning. That was my reasoning for
> pushing for range-ish syntax as well as form.
OTOH I've seen a lot of people bitten by [2014-01-01,2014-12-31] on
TIMESTAMP intervals.
Everybody remembers december has 31 days, but when we have to do
MONTHLY partitions if you use closed intervals someone always miskeys
the number of days, or forgets wheter a particular year is leap or
not, and when doing it automatically I always have to code it as start
+ 1 month - 1day. In my experience having the non-significant part of
the dates ( days in monthly case, months too in yearly cases ) both 1
and equal in start and end makes it easier to check and identify, and
less error prone.
>> But who wants that? People who are doing prefix-based partitioning of
>> their text keys are going to want all of the 'a' things together, and
>> all of the 'b' things in another category. Same for ranges of
>> floating-point numbers, which are also probably an unlikely candidate
>> for a partitioning key anyway.
> /me raises hand. We have tables with a taxonomy in them where the even data
> splits don't fall on single letter boundaries, and often the single string
> values have more rows than entire letters. In those situations, being able
> to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let 'XYZ1' bleed
> into the partition and ['XYZ','XYZ1') lets in other values, and so I go
> chasing down the non-discrete set rabbit hole.
You just do the classical ( I've had to do it ) closed end || minimum
char ( "XYZ","XYZ\0" in this case ). It is not that difficult as
strings have a global order, the next string to any one is always that
plus the \0, or whatever your minimum is.
The problem is with anything similar to a real number, but then there
I've always opted for half-open interval, as they can cover the line
without overlapping, unlike closed ones.
Anyway, as long as anyone makes sure HALF-OPEN intervals are allowed,
I'm fine ( I do not remember the name, but once had to work with a
system that only allowed closed or open and it was a real PITA.
Francisco Olarte.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:09 Robert Haas <robertmhaas@gmail.com>
parent: Francisco Olarte <folarte@peoplecall.com>
1 sibling, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-01 18:09 UTC (permalink / raw)
To: Francisco Olarte <folarte@peoplecall.com>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 2:05 PM, Francisco Olarte <folarte@peoplecall.com> wrote:
>> /me raises hand. We have tables with a taxonomy in them where the even data
>> splits don't fall on single letter boundaries, and often the single string
>> values have more rows than entire letters. In those situations, being able
>> to express ['XYZ','XYZ'] is important. ['XYZ,'XZ') would let 'XYZ1' bleed
>> into the partition and ['XYZ','XYZ1') lets in other values, and so I go
>> chasing down the non-discrete set rabbit hole.
>
> You just do the classical ( I've had to do it ) closed end || minimum
> char ( "XYZ","XYZ\0" in this case ). It is not that difficult as
> strings have a global order, the next string to any one is always that
> plus the \0, or whatever your minimum is.
In defense of Corey's position, that's not so easy. First, \0 doesn't
work; our strings can't include null bytes. Second, the minimum legal
character depends on the collation in use. It's not so easy to figure
out what the "next" string is, even though there necessarily must be
one.
> The problem is with anything similar to a real number, but then there
> I've always opted for half-open interval, as they can cover the line
> without overlapping, unlike closed ones.
>
> Anyway, as long as anyone makes sure HALF-OPEN intervals are allowed,
> I'm fine ( I do not remember the name, but once had to work with a
> system that only allowed closed or open and it was a real PITA.
I think we're all in agreement that half-open intervals should not
only be allowed, but the default. The question is whether it's a good
idea to also allow other possibilities.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:11 Francisco Olarte <folarte@peoplecall.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 0 replies; 329+ messages in thread
From: Francisco Olarte @ 2016-11-01 18:11 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 7:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> In the end, keywords are not the defining issue here; the issue is
> whether all of this complexity around inclusive and exclusive bounds
> carries its weight, and whether we want to be committed to that.
>
> Any other opinions out there?
If it where for me I would opt for just half-open intervals. The only
problem I've ever had with them is when working with FINITE ranges,
i.e., there is no way of expresing the range of 8 bits integer with
half open intervals of 8 bit integers, but I would happily pay that
cost for the benefits of not having people unintentionally make
non-contiguous date/timestamp intervals, which I periodically suffer.
Francisco Olarte.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:11 Corey Huinker <corey.huinker@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 329+ messages in thread
From: Corey Huinker @ 2016-11-01 18:11 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 2:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> Yeah. That syntax has some big advantages, though. If we define that
> partition as START ('2014-01-01') INCLUSIVE END ('2014-12-31')
> INCLUSIVE, there's no way for the system to tell that the there's no
> gap between the that ending bound and the starting bound of the 2015
> partition, because the system has no domain-specific knowledge that
> there is no daylight between 2014-12-31 and 2015-01-01. So if we
> allow things to be specified that way, then people will use that
> syntax and then complain when it doesn't perform quite as well as
> START ('2014-01-01') END ('2015-01-01'). Maybe the difference isn't
> material and maybe we don't care; what do you think?
>
It was a fight I didn't expect to win, and even if we don't get
[x,x]-expressible partitions, at least we're not in the Oracle
context-waterfall, where the lower bound of your partition is determined by
the upper bound of the NEXT partition.
(I really don't want to get tied up adding a system for adding and
> subtracting one to and from arbitrary data types. Life is too short.
> If that requires that users cope with a bit of cognitive dissidence,
> well, it's not the first time something like that will have happened.
> I have some cognitive dissidence about the fact that creat(2) has no
> trailing "e" but truncate(2) does, and moreover the latter can be used
> to make a file longer rather than shorter. But, hey, that's what you
> get for choosing a career in computer science.)
>
That noise your heard was the sound of my dream dying.
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:15 Corey Huinker <corey.huinker@gmail.com>
parent: Francisco Olarte <folarte@peoplecall.com>
1 sibling, 0 replies; 329+ messages in thread
From: Corey Huinker @ 2016-11-01 18:15 UTC (permalink / raw)
To: Francisco Olarte <folarte@peoplecall.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
>
> OTOH I've seen a lot of people bitten by [2014-01-01,2014-12-31] on
> TIMESTAMP intervals.
>
No argument there.
> Everybody remembers december has 31 days, but when we have to do
> MONTHLY partitions if you use closed intervals someone always miskeys
> the number of days, or forgets wheter a particular year is leap or
> not, and when doing it automatically I always have to code it as start
> + 1 month - 1day. In my experience having the non-significant part of
> the dates ( days in monthly case, months too in yearly cases ) both 1
> and equal in start and end makes it easier to check and identify, and
> less error prone.
>
Being able to define partitions by expressions based on the values I want
would be a good thing.
> You just do the classical ( I've had to do it ) closed end || minimum
> char ( "XYZ","XYZ\0" in this case ). It is not that difficult as
> strings have a global order, the next string to any one is always that
> plus the \0, or whatever your minimum is.
>
I've thought about doing that in the past, but wasn't sure it would
actually work correctly. If you have experience with it doing so, that
would be encouraging. How does that *look* when you print your partition
layout though?
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:18 Francisco Olarte <folarte@peoplecall.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Francisco Olarte @ 2016-11-01 18:18 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Robert:
On Tue, Nov 1, 2016 at 7:09 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> In defense of Corey's position, that's not so easy. First, \0 doesn't
> work; our strings can't include null bytes. Second, the minimum legal
> character depends on the collation in use. It's not so easy to figure
> out what the "next" string is, even though there necessarily must be
> one.
I'm aware of that, just wanted to point that it can be done on strings.
> I think we're all in agreement that half-open intervals should not
> only be allowed, but the default. The question is whether it's a good
> idea to also allow other possibilities.
In my experience, people continuously misuse them. I would specially
like to have them disallowed on timestamp columns ( and other
real-like data, including numeric ). But knowing they cannot do a few
things, and some others are easier with them is enough for allowing
them as an explicit non default for me.
Francisco Olarte.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:24 Robert Haas <robertmhaas@gmail.com>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-01 18:24 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 2:11 PM, Corey Huinker <corey.huinker@gmail.com> wrote:
> On Tue, Nov 1, 2016 at 2:01 PM, Robert Haas <robertmhaas@gmail.com> wrote:
>> Yeah. That syntax has some big advantages, though. If we define that
>> partition as START ('2014-01-01') INCLUSIVE END ('2014-12-31')
>> INCLUSIVE, there's no way for the system to tell that the there's no
>> gap between the that ending bound and the starting bound of the 2015
>> partition, because the system has no domain-specific knowledge that
>> there is no daylight between 2014-12-31 and 2015-01-01. So if we
>> allow things to be specified that way, then people will use that
>> syntax and then complain when it doesn't perform quite as well as
>> START ('2014-01-01') END ('2015-01-01'). Maybe the difference isn't
>> material and maybe we don't care; what do you think?
>
> It was a fight I didn't expect to win, and even if we don't get
> [x,x]-expressible partitions, at least we're not in the Oracle
> context-waterfall, where the lower bound of your partition is determined by
> the upper bound of the NEXT partition.
I certainly agree that was a horrible design decision on Oracle's
part. It's really messy. If you drop a partition, it changes the
partition constraint for the adjacent partition. Then you want to add
the partition back, say, but you have to first check whether the
adjacent partition, whose legal range has been expanded, has any
values that are out of bounds. Which it can't, but you don't know
that, so you have to scan the whole partition. Aargh! Maybe this
somehow works out in their system - I'm not an Oracle expert - but I
think it's absolutely vital that we don't replicate it into
PostgreSQL. (I have some, ahem, first-hand knowledge of how that
works out.)
>> (I really don't want to get tied up adding a system for adding and
>> subtracting one to and from arbitrary data types. Life is too short.
>> If that requires that users cope with a bit of cognitive dissidence,
>> well, it's not the first time something like that will have happened.
>> I have some cognitive dissidence about the fact that creat(2) has no
>> trailing "e" but truncate(2) does, and moreover the latter can be used
>> to make a file longer rather than shorter. But, hey, that's what you
>> get for choosing a career in computer science.)
>
> That noise your heard was the sound of my dream dying.
Well, I'm not sure we've exactly reached consensus here, and you're
making me feel like I just kicked a puppy. However, my goal here is
not to kill your dream but to converge on a committable patch as
quickly as possible. Adding increment/decrement operators to every
discrete(-ish) type we have is not the way to accomplish that. To the
contrary, that's just about guaranteed to make this patch take an
extra release cycle to finish. Now, that does not necessarily mean we
can't keep the INCLUSIVE/EXCLUSIVE stuff -- after all, Amit has
already written it -- but then we have to live with the fact that
+1/-1 based optimizations and matching are not going to be there.
Whether it's still worth having fully-open and fully-closed ranges on
general principle -- and whether the lack of such optimizations
changes the calculus -- is what we are now debating.
More votes welcome. Right now I count 2 votes for keeping the
inclusive-exclusive stuff (Amit, Corey) and two for nuking it from
orbit (Robert, Francisco). I'm not going to put my foot down on this
point against a contrary consensus, so please chime in. Thanks.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-01 18:36 Corey Huinker <corey.huinker@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Corey Huinker @ 2016-11-01 18:36 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 2:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> Well, I'm not sure we've exactly reached consensus here, and you're
> making me feel like I just kicked a puppy.
>
It was hyperbole. I hope you found it as funny to read as I did to write.
This is a great feature and I'm not going to make "perfect" the enemy of
"good".
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-02 02:55 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-02 02:55 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/02 2:53, Robert Haas wrote:
> On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> [ new patches ]
>
> Reviewing 0005:
>
> Your proposed commit messages says this:
>
>> If relation is the target table (UPDATE and DELETE), flattening is
>> done regardless (scared to modify inheritance_planner() yet).
I should have explained my thinking behind why I ended up not handling the
result relation case:
While set_append_rel_size() can be safely invoked recursively on the roots
of partition sub-trees, I was not quite sure if inheritance_planner() is
amenable to such recursive invocation. While the former is relatively
straightforward baserel processing, the latter is not-so-straightforward
transformation of the whole query for every target child relation of the
target parent.
> In the immortal words of Frank Herbert: “I must not fear. Fear is the
> mind-killer. Fear is the little-death that brings total obliteration.
> I will face my fear. I will permit it to pass over me and through me.
> And when it has gone past I will turn the inner eye to see its path.
> Where the fear has gone there will be nothing. Only I will remain.”
>
> In other words, I'm not going to accept fear as a justification for
> randomly-excluding the target-table case from this code. If there's
> an actual reason not to do this in that case or some other case, then
> let's document that reason. But weird warts in the code that are
> justified only by nameless anxiety are not good.
Perhaps the above comment expands a little on the actual reason. Though I
guess it still amounts to giving up on doing a full analysis of whether
recursive processing within inheritance_planner() is feasible.
I think we could just skip this patch as long as a full investigation into
inheritance_planner() issues is not done. It's possible that there might
be other places in the planner code which are not amenable to the proposed
multi-level AppendRelInfos. Ashutosh had reported one [1], wherein
lateral joins wouldn't work with multi-level partitioned table owing to
the multi-level AppendRelInfos (the patch contains a hunk to address that).
> Of course, the prior question is whether we should EVER be doing this.
> I realize that something like this MAY be needed for partition-wise
> join, but the mission of this patch is not to implement partition-wise
> join. Does anything in this patch series really require this? If so,
> what? If not, how about we leave it out and refactor it when that
> change is really needed for something?
Nothing *requires* it as such. One benefit I see is that exclusion of the
root of some partition sub-tree means the whole sub-tree is excluded in
one go. Currently, because of the flattening, every relation in that
sub-tree would be excluded separately, needlessly repeating the expensive
constraint exclusion proof. But I guess that's just an optimization.
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CAFjFpReZF34MDbY95xoATi0xVj2mAry4-LHBWVBayOc8gj%3Diqg%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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-02 07:41 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-02 07:41 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/02 2:34, Robert Haas wrote:
> On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> [ new patches ]
>
> Reviewing 0006:
Thanks for the review!
> This patch seems scary. I sort of assumed from the title -- "Teach a
> few places to use partition check quals." -- that this was an optional
> thing, some kind of optimization from which we could reap further
> advantage once the basic infrastructure was in place. But it's not
> that at all. It's absolutely necessary that we do this, or data
> integrity is fundamentally compromised. How do we know that we've
> found all of the places that need to be taught about these new,
> uncatalogued constraints?
Making this a separate commit from 0003 was essentially to avoid this
getting lost among all of its other changes. In fact, it was to bring to
notice for closer scrutiny whether all the sites in the backend code that
are critical for data integrity in face of the implicit partition
constraints are being informed about those constraints.
> I'm feeling fairly strongly like you should rewind and make the
> partitioning constraints normal catalogued constraints. That's got a
> number of advantages, most notably that we can be sure they will be
> properly enforced by the entire system (modulo existing bugs, of
> course). Also, they'll show up automatically in tools like psql's \d
> output, pgAdmin, and anything else that is accustomed to being able to
> find constraints in the catalog. We do need to make sure that those
> constraints can't be dropped (or altered?) inappropriately, but that's
> a relatively small problem. If we stick with the design you've got
> here, every client tool in the world needs to be updated, and I'm not
> seeing nearly enough advantage in this system to justify that kind of
> upheaval.
As for which parts of the system need to know about these implicit
partition constraints to *enforce* them for data integrity, we could say
that it's really just one site - ExecConstraints() called from
ExecInsert()/ExecUpdate().
Admittedly, the current error message style as in this patch exposes the
implicit constraint approach to a certain criticism: "ERROR: new row
violates the partition boundary specification of \"%s\"". It would say
the following if it were a named constraint: "ERROR: new row for relation
\"%s\" violates check constraint \"%s\""
For constraint exclusion optimization, we teach get_relation_constraints()
to look at these constraints. Although greatly useful, it's not the case
of being absolutely critical.
Beside the above two cases, there is bunch of code (relcache, DDL) that
looks at regular constraints, but IMHO, we need not let any of that code
concern itself with the implicit partition constraints. Especially, I
wonder why the client tools should want the implicit partitioning
constraint to be shown as a CHECK constraint? As the proposed patch 0004
(psql) currently does, isn't it better to instead show the partition
bounds as follows?
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+\d part_b
+ Table "public.part_b"
+ Column | Type | Modifiers
+--------+---------+--------------------
+ a | text |
+ b | integer | not null default 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+ "part_b_b_check" CHECK (b >= 0)
Needless to say, that could save a lot of trouble thinking about
generating collision-free names of these constraints, their dependency
handling, unintended altering of these constraints, pg_dump, etc.
> In fact, as far as I can see, the only advantage of this approach is
> that when the insert arrives through the parent and is routed to the
> child by whatever tuple-routing code we end up with (I guess that's
> what 0008 does), we get to skip checking the constraint, saving CPU
> cycles. That's probably an important optimization, but I don't think
> that putting the partitioning constraint in the catalog in any way
> rules out the possibility of performing that optimization. It's just
> that instead of having the partitioning excluded-by-default and then
> sometimes choosing to include it, you'll have it included-by-default
> and then sometimes choose to exclude it.
Hmm, doesn't it seem like we would be making *more* modifications to the
existing code (backend or otherwise) to teach it about excluding the
implicit partition constraints than the other way around? The other way
around being to modify the existing code to include the implicit
constraints which is what this patch is about.
Having said all that, I am open to switching to the catalogued partition
constraints if the arguments I make above in favor of this patch are not
all that sound.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-02 08:32 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-02 08:32 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/02 16:41, Amit Langote wrote:
> Having said all that, I am open to switching to the catalogued partition
> constraints if the arguments I make above in favor of this patch are not
> all that sound.
One problem I didn't immediately see a solution for if we go with the
catalogued partition constraints is how do we retrieve a relation's
partition constraint? There are a couple of cases where that becomes
necessary. Consider that we are adding a partition to a partitioned table
that is itself partition. In this case, the new partition's constraint
consists of whatever we generate from its FOR VALUES specification *ANDed*
with the parent's constraint. We must somehow get hold of the latter.
Which of the parent's named check constraints in the pg_constraint catalog
is supposed to be the partition constraint? With the uncatalogued
partition constraints approach, we simply generate it from the parent's
pg_class.relpartbound (or we might get the relcache copy of the same
stored in rd_partcheck). Hmm.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-02 10:47 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-02 10:47 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/02 2:44, Robert Haas wrote:
> On Fri, Oct 28, 2016 at 3:53 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> Insisting that you can't drop a child without detaching it first seems
>>> wrong to me. If I already made this comment and you responded to it,
>>> please point me back to whatever you said. However, my feeling is
>>> this is flat wrong and absolutely must be changed.
>>
>> I said the following [1]:
>>
>> | Hmm, I don't think I like this. Why should it be necessary to detach
>> | a partition before dropping it? That seems like an unnecessary step.
>>
>> I thought we had better lock the parent table when removing one of its
>> partitions and it seemed a bit odd to lock the parent table when dropping
>> a partition using DROP TABLE? OTOH, with ALTER TABLE parent DETACH
>> PARTITION, the parent table is locked anyway.
>
> That "OTOH" part seems like a pretty relevant point.
>
> Basically, I think people expect to be able to say "DROP THINGTYPE
> thingname" or at most "DROP THINGTYPE thingname CASCADE" and have that
> thing go away. I'm opposed to anything which requires some other
> series of steps without a very good reason, and possible surprise
> about the precise locks that the command requires isn't a good enough
> reason from my point of view.
OK, I changed things so that DROP TABLE a_partition no longer complains
about requiring to detach first. Much like how index_drop() locks the
parent table ('parent' in a different sense, of course) and later
invalidates its relcache, heap_drop_with_catalog(), if the passed in relid
is a partition, locks the parent table using AccessExclusiveLock, performs
its usual business, and finally invalidates the parent's relcache before
closing it without relinquishing the lock. Does that sounds sane? One
downside is that if the specified command is DROP TABLE parent CASCADE,
the above described invalidation is a waste of cycles because the parent
will be dropped anyway after all the partitions are dropped.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-03 11:46 alvherre@alvh.no-ip.org
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 1 reply; 329+ messages in thread
From: alvherre@alvh.no-ip.org @ 2016-11-03 11:46 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
El 2016-10-28 07:53, Amit Langote escribió:
> @@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab,
> Relation rel,
> * Validity checks (permission checks wait till we have the column
> * numbers)
> */
> + if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
> + ereport(ERROR,
> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
> + errmsg("cannot reference relation \"%s\"",
> RelationGetRelationName(pkrel)),
> + errdetail("Referencing partitioned tables in foreign key
> constraints is not supported.")));
Is there a plan for fixing this particular limitation? It's a pretty
serious problem for users,
and the suggested workaround (to create a separate non-partitioned table
which carries only the PK
columns which is updated by triggers, and direct the FKs to it instead
of to the partitioned table)
is not only a very ugly one, but also very slow.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-03 15:47 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-03 15:47 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Nov 2, 2016 at 6:47 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> OK, I changed things so that DROP TABLE a_partition no longer complains
> about requiring to detach first. Much like how index_drop() locks the
> parent table ('parent' in a different sense, of course) and later
> invalidates its relcache, heap_drop_with_catalog(), if the passed in relid
> is a partition, locks the parent table using AccessExclusiveLock, performs
> its usual business, and finally invalidates the parent's relcache before
> closing it without relinquishing the lock. Does that sounds sane?
Yes.
> One
> downside is that if the specified command is DROP TABLE parent CASCADE,
> the above described invalidation is a waste of cycles because the parent
> will be dropped anyway after all the partitions are dropped.
I don't think that's even slightly worth worrying about.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-03 15:49 Robert Haas <robertmhaas@gmail.com>
parent: alvherre@alvh.no-ip.org
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-03 15:49 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Nov 3, 2016 at 7:46 AM, <alvherre@alvh.no-ip.org> wrote:
> El 2016-10-28 07:53, Amit Langote escribió:
>> @@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab,
>> Relation rel,
>> * Validity checks (permission checks wait till we have the column
>> * numbers)
>> */
>> + if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>> + errmsg("cannot reference relation
>> \"%s\"", RelationGetRelationName(pkrel)),
>> + errdetail("Referencing partitioned tables
>> in foreign key constraints is not supported.")));
>
> Is there a plan for fixing this particular limitation? It's a pretty
> serious problem for users,
> and the suggested workaround (to create a separate non-partitioned table
> which carries only the PK
> columns which is updated by triggers, and direct the FKs to it instead of to
> the partitioned table)
> is not only a very ugly one, but also very slow.
If you have two compatibly partitioned tables, and the foreign key
matches the partitioning keys, you could implement a foreign key
between the two tables as a foreign key between each pair of matching
partitions. Otherwise, isn't the only way to handle this a global
index?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-03 15:52 Robert Haas <robertmhaas@gmail.com>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-03 15:52 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 1, 2016 at 2:36 PM, Corey Huinker <corey.huinker@gmail.com> wrote:
> On Tue, Nov 1, 2016 at 2:24 PM, Robert Haas <robertmhaas@gmail.com> wrote:
>> Well, I'm not sure we've exactly reached consensus here, and you're
>> making me feel like I just kicked a puppy.
>
> It was hyperbole. I hope you found it as funny to read as I did to write.
> This is a great feature and I'm not going to make "perfect" the enemy of
> "good".
Oh, OK. Sorry, the tone did not transfer to my brain in the way you
intended it - I thought you were actually upset.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-03 16:37 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-03 16:37 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Nov 2, 2016 at 3:41 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> As for which parts of the system need to know about these implicit
> partition constraints to *enforce* them for data integrity, we could say
> that it's really just one site - ExecConstraints() called from
> ExecInsert()/ExecUpdate().
Well, that's a slightly optimistic view of the situation, because the
issue is whether ExecConstraints() is going to get called in the first
place. But now that I look at it, there are only a handful of
callers, so maybe it's not so bad.
> Admittedly, the current error message style as in this patch exposes the
> implicit constraint approach to a certain criticism: "ERROR: new row
> violates the partition boundary specification of \"%s\"". It would say
> the following if it were a named constraint: "ERROR: new row for relation
> \"%s\" violates check constraint \"%s\""
>
> For constraint exclusion optimization, we teach get_relation_constraints()
> to look at these constraints. Although greatly useful, it's not the case
> of being absolutely critical.
>
> Beside the above two cases, there is bunch of code (relcache, DDL) that
> looks at regular constraints, but IMHO, we need not let any of that code
> concern itself with the implicit partition constraints. Especially, I
> wonder why the client tools should want the implicit partitioning
> constraint to be shown as a CHECK constraint? As the proposed patch 0004
> (psql) currently does, isn't it better to instead show the partition
> bounds as follows?
>
> +CREATE TABLE part_b PARTITION OF parted (
> + b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
> + CONSTRAINT check_a CHECK (length(a) > 0)
> +) FOR VALUES IN ('b');
Good point.
> +\d part_b
> + Table "public.part_b"
> + Column | Type | Modifiers
> +--------+---------+--------------------
> + a | text |
> + b | integer | not null default 1
> +Partition of: parted FOR VALUES IN ('b')
> +Check constraints:
> + "check_a" CHECK (length(a) > 0)
> + "part_b_b_check" CHECK (b >= 0)
>
> Needless to say, that could save a lot of trouble thinking about
> generating collision-free names of these constraints, their dependency
> handling, unintended altering of these constraints, pg_dump, etc.
Well, that's certainly true, but those problems don't strike me as so
serious that they couldn't be solved with a reasonable amount of
labor.
>> In fact, as far as I can see, the only advantage of this approach is
>> that when the insert arrives through the parent and is routed to the
>> child by whatever tuple-routing code we end up with (I guess that's
>> what 0008 does), we get to skip checking the constraint, saving CPU
>> cycles. That's probably an important optimization, but I don't think
>> that putting the partitioning constraint in the catalog in any way
>> rules out the possibility of performing that optimization. It's just
>> that instead of having the partitioning excluded-by-default and then
>> sometimes choosing to include it, you'll have it included-by-default
>> and then sometimes choose to exclude it.
>
> Hmm, doesn't it seem like we would be making *more* modifications to the
> existing code (backend or otherwise) to teach it about excluding the
> implicit partition constraints than the other way around? The other way
> around being to modify the existing code to include the implicit
> constraints which is what this patch is about.
I'm not sure which way is actually going to be more code modification,
but I guess you've persuaded me that the way you have it is
reasonable, so let's stick with that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-04 00:16 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-04 00:16 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Apologies if I've made some of these comments before and/or missed
comments you've made on these topics. The size of this patch set is
so large that it's hard to keep track of everything.
Re-reviewing 0001:
+ indicate which table columns are used as partition key. For example,
s/are used as/are part of 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.
I think you can leave out "over the table columns".
+ columns or expressions forms the <firstterm>partitioning key</firstterm>
s/forms/form/
+ The table itself is empty. A data row inserted into the table is routed
s/The table/The partitioned table/
+ * Anything mentioned in the expressions. We must ignore the column
+ * references which will count as self-dependency items; in this case,
+ * the depender is the table itself (there is no such thing as partition
+ * key object).
"depender" is not really a word, and the parenthetical note isn't very
clear. Maybe: We must ignore the column references, which will depend
on the table itself; there is no separate partition key object.
+ heap_close(pg_partitioned_table, RowExclusiveLock);
It seems like it would be safe to do this right after
CatalogUpdateIndexes(pg_partitioned_table, tuple), and I'd recommend
that you do. Not for performance or anything, but just to keep
related code together.
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
Perhaps we should rename this function to ResolveOpClass, since it's
now going to be used for both indexes and partitioning keys.
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
is_expr doesn't seem quite as clear as, say, used_by_expr or used_in_expr.
Also, the specification for this function doesn't seem to be very
clear about what this is supposed to do if the same column is both an
explicit partitioning column and also used in an expression, and the
code looks like it'll return with *is_expr set based on whichever use
it encounters first. If that's intended behavior, maybe add a comment
like: It's possible for a column to be used both directly and as part
of a partition key expression; if that happens, *is_expr may end up as
either true or false. That's OK for current uses of this function,
because *is_expr is only used to tailor the error message text.
+ if (is_expr)
+ *is_expr = false;
+ if (attnum == partattno)
+ return true;
I think you should adjust this (and the other bit in the same
function) so that you don't set *is_expr until you're committed to
returning.
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno == attnum)
+ return true;
+ }
How about bms_is_member(expr_attrs, attnum -
FirstLowInvalidHeapAttributeNumber), instead of looping?
+ errmsg("cannot reference relation \"%s\"",
RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign
key constraints is not supported.")));
I think you could get rid of the errdetail and just have the error
message be "cannot reference partitioned table \"%s\"".
+ errmsg("column \"%s\" appears twice in
partition key", pelem->name),
It could be there three times! How about column \"%s\" appears more
than once in partition key? (I see that you seem to have adapted this
from some code in parse_utilcmd.c, which perhaps should also be
adjusted, but that's a job for another patch.)
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
But you don't, right? Unless I am confused, which is possible, the
latter COLLATE will be ignored, while the former one will set the
collation to be used in the context of partitioning comparisons.
Re-reviewing 0002:
+ if (fout->remoteVersion >= 100000)
+ {
+ PQExpBuffer acl_subquery = createPQExpBuffer();
+ PQExpBuffer racl_subquery = createPQExpBuffer();
+ PQExpBuffer initacl_subquery = createPQExpBuffer();
+ PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+ PQExpBuffer attacl_subquery = createPQExpBuffer();
+ PQExpBuffer attracl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitracl_subquery = createPQExpBuffer();
It seems unnecessary to repeat all of this. The only differences
between the 10000 version and the 9600 version are:
60,61c60
< "AS changed_acl, "
< "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
---
> "AS changed_acl "
73c72
< "WHERE c.relkind in ('%c', '%c', '%c', '%c',
'%c', '%c', '%c') "
---
> "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
87,88c86
< RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
< RELKIND_PARTITIONED_TABLE);
---
> RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
But none of that is really a problem. Sure, the 'P' case will never
arise in 9.6, but so what? I'd really like to not keep duplicating
these increasingly-complex hunks of code if we can find some way to
avoid that.
/* 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')");
I think you need to update the comment. "Find inheritance
information, excluding implicit inheritance via partitioning. We're
not interested in that case because $REASON."
Re-reviewing 0003:
+ <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.
+ </para>
This would, presumably, also be true for inheritance. I think we
could just leave this out.
+ as partition of the target table. The partition bound specification must
s/as partition/as a partition/
+ correspond to the partitioning method and partitioning key of the target
I think that in most places were are referring to the "partitioning
method" (with ing) but the "partition key" (without ing). Let's try to
be consistent.
+ table. The table to be attached must have all the columns as the target
+ table and no more; moreover, the column types must also match. Also, it
+ must have all the matching constraints as the target table.
s/all the columns/all of the same columns/
The second sentence doesn't seem quite grammatical. And why would
that be true anyway? Partitions can have extra constraints, and if
they lack constraints that are present on the partitioned table, those
constraints will be added and validated, right?
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the
<literal>VALIDATE</literal>
+ option were specified.
I don't think it's OK to allow the partition to be added if it
contains rows that might not be valid. We are generally vary wary
about adding options that can cause data integrity failures and I
think we shouldn't start here, either. On the other hand, it's also
not desirable for adding a partition to take O(n) time in all cases.
So what would be nice is if the new partition could self-certify that
contains no problematic rows by having a constraint that matches the
new partitioning constraint. Then we can skip the scan if that
constraint is already present.
+ inherited columns. One can also specify table constraints, in addition
Delete comma.
+ to those inherited from the parent. If a check constraint with the name
+ matching one of the parent's constraint is specified, it is merged with
+ the latter, provided the specified condition is same.
Doesn't that effectively delete the merged constraint?
Suggest: "If the parent already has a check constraint with the same
name as a constraint specified for the child, the conditions must be
the same."
+) FOR VALUES IN ('los angeles', 'san fransisco');
That's not you you spell San Francisco.
+ Create partition of a list partitioned table that itself is further
+ partitioned and then create its partition:
s/itself is/is itself/
s/then create its partition/then add a partition to it/
+ if (!is_local || con->coninhcount == 0)
+ con->coninhcount++;
I would think that you could get rid of the "if" and just say
con->coninhcount = 1. It seems to me (and perhaps the comment could
say this) that for a partitioned table, we can simplify the handling
of coninhcount and conislocal. Since partitioning hierarchies don't
allow multiple inheritance, any inherited constraint must be inherited
exactly once. Since a partition cannot have inheritance children --
except by being partitioned itself -- there is no point in tracking
conislocal, so we always store it as false.
+
+void
+StorePartitionBound(Relation rel, Node *bound)
Header comment, please!
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
We try not to do unnecessary work in non-assert-enabled builds solely
for the benefit of assert-enabled builds. We also try not to end up
with variables that are only used in assert-enabled builds but not
marked PG_USED_FOR_ASSERTS_ONLY, because those tend to cause compiler
warnings. I'm not sure an compiler would be smart enough to warn
about this, but I suggest adding an #ifdef USE_ASSERT_CHECKING with a
block inside where the offending variable is declared. Actually, I
think you need to move a bit more code. Hmm. Something like:
#ifdef USE_ASSERT_CHECKING
{
Form_pg_class classForm;
bool isnull;
classForm = (Form_pg_class) GETSTRUCT(tuple);
Assert(!classForm->relispartition);
(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
&isnull);
Assert(isnull);
}
#endif
+ * are same in common cases, of which we only store one.
"and we only store one of them."
+ /*
+ * Never put a
null into the values array, flag
+ * instead for
the code further down below where
+ * we
construct the actual relcache struct.
+ */
+
found_null_partition = true;
+
null_partition_index = i;
How about we elog(ERROR, ...) if found_null_partition is already set?
+ foreach(cell, non_null_values)
+ {
+ PartitionListValue *src =
lfirst(cell);
+
+ all_values[i] = (PartitionListValue *)
+
palloc(sizeof(PartitionListValue));
+ all_values[i]->value =
datumCopy(src->value,
+
key->parttypbyval[0],
+
key->parttyplen[0]);
+ all_values[i]->index = src->index;
+ i++;
+ }
Why do we need to datumCopy() here when we just did that in the
previous loop? Why did the previous loop need to datumCopy(), anyway?
Can't we just pass the same datums around? I understand that you
need to copy the data into the correct context at the end, but doing
two copies prior to that seems excessive.
+get_leaf_partition_oids(Oid relid, int lockmode)
Instead of implementing this, can you just use find_all_inheritors()?
+ /*
+ * Is lower_val = upper_val?
+ */
+ if (lower_val && upper_val)
So, that comment does not actually match that code. That if-test is
just checking whether both bounds are finite. What I think you should
be explaining here is that if lower_val and upper_val are both
non-infinite, and if the happen to be equal, we want to emit an
equality test rather than a >= test plus a <= test because $REASON.
+ operoid = get_opfamily_member(key->partopfamily[col],
+
key->parttypid[col],
+
key->parttypid[col],
+ strategy);
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+
key->partopcintype[col],
+
key->partopcintype[col],
+
strategy);
+ *need_relabel = true;
+ }
There's a comment explaining THAT you do this ("Use either the column
type as the operator datatype or opclass's declared input type.") but,
to repeat a complaint that I've made often, nothing explaining why.
In this particular case, what's not clear - in my opinion - is why you
need to try two different possibilities, and why at least one of those
possibilities is guaranteed to work. I gather that if the opfamily
doesn't contain an operator for the actual type of the partitioning
column, you think it will certainly contain one for the input type of
the operator class (which seems right), and that the input type of the
operator class will be binary-compatible with the type of the
partitioning column (which is less-obviously true, and needs some
explanation).
I also think that this function should elog(ERROR, ...) if by any
chance the second get_opfamily_member() call also fails. Otherwise the
error might occur quite a bit downstream and be hard to understand.
+ ReleaseSysCache(tuple);
+ heap_close(parent, AccessShareLock);
I think you had better not release the lock here - i.e. pass NoLock.
We don't release locks on tables until transaction commit, except for
catalog tables. This also comes up in, at least,
transformPartitionOf().
+ PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+ PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_rbound_cmp(key, b1, b2);
Whitespace.
+ * as partition and schema consists of columns definitions corresponding
the schema consists
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE)
I would instead error out if ONLY is specified for a partitioned table.
+ /*
+ * If the table is source table of ATTACH PARTITION
command, following
+ * check is unnecessary.
+ */
As usual, comment should say WHY.
+ if (partqualstate && !ExecQual(partqualstate,
econtext, true))
+ ereport(ERROR,
+
(errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("child table
contains a row violating partition bound specification")));
Why not mimic the existing phrasing? "partition constraint is
violated by some row"
What happens if you try to attach a table as a partition of itself or
one of its ancestors?
- errmsg("column \"%s\" in child table
must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\"
in child table must be marked NOT NULL",
+
attributeName)));
Whitespace-only hunk; revert. You cannot fight the power of pgindent.
+ errmsg("cannot attach table that is a
inheritance child as partition")));
an inheritance child
+ errmsg("cannot attach a temporary relation of another
session as partition ")));
Extra space.
+ errdetail("Table being
attached should contain only the columns"
+ " present
in parent.")));
Suggest: "New partition should contain only..."
Also, don't break error messages into multiple strings. Make it one
long string and let pgindent deal.
+ | FOR VALUES START '(' range_datum_list ')' lb_inc
+ END_P '('
range_datum_list ')' ub_inc
Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
idiomatic than START and END?
+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);
+
If there's anyway that we might do heap_openrv() on the same RangeVar
at multiple places in the code, it presents security and integrity
hazards because the referent of that RangeVar might change in the
meantime. I suspect there is such a risk here - won't we need to open
the relation again when we actually want to do the operation?
Also, we should never acquire a lower-level lock on a relation and
then, further downstream, acquire a higher-level lock on the same
object. To do so creates a deadlock hazard. That seems like it might
be a problem here, too.
+ /*if (ldatum->infinite && rdatum->infinite)
+ ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START
and END datum for column \"%s\" cannot be UNBOUNDED",
+ colname),
+
parser_errposition(cxt->pstate, rdatum->location)));*/
Commented-out code is bad.
+ if (list_length(spec->lowerdatums) > partnatts)
+ ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values
than the length of the partition key"),
+
parser_errposition(cxt->pstate,
+
exprLocation(list_nth(spec->lowerdatums,
+
list_length(spec->lowerdatums) - 1)))));
+ else if (list_length(spec->lowerdatums) < partnatts)
+ ereport(ERROR,
+
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer
values than the length of the partition key"),
+
parser_errposition(cxt->pstate,
+
exprLocation(list_nth(spec->lowerdatums,
+
list_length(spec->lowerdatums) - 1)))));
It would be worth looking for a way to unify these cases. Like "START
must specify exactly one value per partitioning column".
+ * Same oids? No mind order - in the list case, it
matches the order
+ * in which partition oids are returned by a
pg_inherits scan, whereas
+ * in the range case, they are in order of ranges of individual
+ * partitions. XXX - is the former unsafe?
Probably. It likely depends on the physical ordering of tuples in the
table, which can change.
+ * BoundCollection encapsulates a set of partition bounds of either physical
+ * or logical relations. It is associated with a partitioned relation of
+ * which the aforementioned relations are partitions.
"physical or logical relations" is unfamiliar terminology.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-07 17:15 Jaime Casanova <jaime.casanova@2ndquadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
6 siblings, 2 replies; 329+ messages in thread
From: Jaime Casanova @ 2016-11-07 17:15 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> Please find attached the latest version of the patches
Hi,
I started to review the functionality of this patch, so i applied all
9 patches. After that i found this warning, which i guess is because
it needs a cast.
After that, i tried a case that i think is important: to partition an
already existing table. Because there is no ALTER TABL SET PARTITION
or something similar (which i think makes sense because such a command
would need to create the partitions and move the rows to follow the
rule that there is no rows in a parent table).
So, what i tried was:
1) rename original table
2) create a new partitioned table with old name
3) attach old table as a partition with bounds outside normal bounds
and no validate
the idea is to start attaching valid partitions and delete and insert
rows from the invalid one (is there a better way of doing that?), that
will allow to partition a table easily.
So far so good, until i decided points 1 to 3 should happen inside a
transaction to make things transparent to the user.
Attached is script that shows the failure when trying it:
script 1 (failing_test_1.sql) fails the assert
"Assert(RelationGetPartitionKey(parentRel) != NULL);" in
transformAttachPartition() at src/backend/parser/parse_utilcmd.c:3164
After that i tried the same but with an already partitioned (via
inheritance) table and got this (i did this first without a
transaction, doing it with a transaction will show the same failure as
before):
script 2 (failing_test_2.sql) fails the assert
"Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);" in
expand_inherited_rte_internal() at
src/backend/optimizer/prep/prepunion.c:1551
PS: i don't like the START END syntax but i don't have any ideas
there... except, there was a reason not to use expressions (like a
CHECK constraint?)
--
Jaime Casanova www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/sql] failing_test_1.sql (461B, ../../CAJGNTeMKP0+QBUrSQMgzAc0zwBTuCGCRWzRE=8T26oN4ZOFtxA@mail.gmail.com/2-failing_test_1.sql)
download
[application/sql] failing_test_2.sql (810B, ../../CAJGNTeMKP0+QBUrSQMgzAc0zwBTuCGCRWzRE=8T26oN4ZOFtxA@mail.gmail.com/3-failing_test_2.sql)
download
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-07 17:24 Jaime Casanova <jaime.casanova@2ndquadrant.com>
parent: Jaime Casanova <jaime.casanova@2ndquadrant.com>
1 sibling, 1 reply; 329+ messages in thread
From: Jaime Casanova @ 2016-11-07 17:24 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 7 November 2016 at 12:15, Jaime Casanova
<jaime.casanova@2ndquadrant.com> wrote:
> On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>
>> Please find attached the latest version of the patches
>
> Hi,
>
> I started to review the functionality of this patch, so i applied all
> 9 patches. After that i found this warning, which i guess is because
> it needs a cast.
>
oh! i forgot the warning
"""
partition.c: In function ‘get_qual_for_list’:
partition.c:1159:6: warning: assignment from incompatible pointer type
or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
^
"""
attached a list of the warnings that my compiler give me (basically
most are just variables that could be used uninitialized)
--
Jaime Casanova www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
partition.c: In function ‘get_qual_for_list’:
partition.c:1159:6: warning: assignment from incompatible pointer type
or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
^
partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:250:8: warning: ‘num_distinct_bounds’ may be used uninitialized in this function [-Wmaybe-uninitialized]
int num_distinct_bounds;
^
partition.c:587:9: warning: ‘distinct_bounds’ may be used uninitialized in this function [-Wmaybe-uninitialized]
copy_range_bound(key, distinct_bounds[i]);
^
partition.c:536:5: warning: ‘all_values_count’ may be used uninitialized in this function [-Wmaybe-uninitialized]
for (i = 0; i < all_values_count; i++)
^
partition.c:243:23: warning: ‘all_values’ may be used uninitialized in this function [-Wmaybe-uninitialized]
PartitionListValue **all_values;
^
partition.c:619:35: warning: ‘oids’ may be used uninitialized in this function [-Wmaybe-uninitialized]
result->oids[mapping[i]] = oids[i];
^
partition.c: In function ‘get_qual_from_partbound’:
partition.c:973:10: warning: ‘my_qual’ may be used uninitialized in this function [-Wmaybe-uninitialized]
my_qual = (List *) map_variable_attnos((Node *) my_qual,
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1734:8: warning: ‘index’ may be used uninitialized in this function [-Wmaybe-uninitialized]
if (node->index > index)
^
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/plain] warnings.txt (1.5K, ../../CAJGNTePfg9M9smGcWQR-WdjrnU3U5uppTwJahcD-t1pBqO1=Lw@mail.gmail.com/2-warnings.txt)
download | inline:
partition.c: In function ‘get_qual_for_list’:
partition.c:1159:6: warning: assignment from incompatible pointer type
or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
^
partition.c: In function ‘RelationBuildPartitionDesc’:
partition.c:250:8: warning: ‘num_distinct_bounds’ may be used uninitialized in this function [-Wmaybe-uninitialized]
int num_distinct_bounds;
^
partition.c:587:9: warning: ‘distinct_bounds’ may be used uninitialized in this function [-Wmaybe-uninitialized]
copy_range_bound(key, distinct_bounds[i]);
^
partition.c:536:5: warning: ‘all_values_count’ may be used uninitialized in this function [-Wmaybe-uninitialized]
for (i = 0; i < all_values_count; i++)
^
partition.c:243:23: warning: ‘all_values’ may be used uninitialized in this function [-Wmaybe-uninitialized]
PartitionListValue **all_values;
^
partition.c:619:35: warning: ‘oids’ may be used uninitialized in this function [-Wmaybe-uninitialized]
result->oids[mapping[i]] = oids[i];
^
partition.c: In function ‘get_qual_from_partbound’:
partition.c:973:10: warning: ‘my_qual’ may be used uninitialized in this function [-Wmaybe-uninitialized]
my_qual = (List *) map_variable_attnos((Node *) my_qual,
^
partition.c: In function ‘get_partition_for_tuple’:
partition.c:1734:8: warning: ‘index’ may be used uninitialized in this function [-Wmaybe-uninitialized]
if (node->index > index)
^
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-08 04:08 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Jaime Casanova <jaime.casanova@2ndquadrant.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-08 04:08 UTC (permalink / raw)
To: Jaime Casanova <jaime.casanova@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Hi Jaime,
On 2016/11/08 2:24, Jaime Casanova wrote:
> On 7 November 2016 at 12:15, Jaime Casanova
> <jaime.casanova@2ndquadrant.com> wrote:
>> On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>
>>> Please find attached the latest version of the patches
>>
>> Hi,
>>
>> I started to review the functionality of this patch, so i applied all
>> 9 patches. After that i found this warning, which i guess is because
>> it needs a cast.
>>
>
> oh! i forgot the warning
> """
> partition.c: In function ‘get_qual_for_list’:
> partition.c:1159:6: warning: assignment from incompatible pointer type
> or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
> ^
> """
This one I noticed too and have fixed.
>
> attached a list of the warnings that my compiler give me (basically
> most are just variables that could be used uninitialized)
Thanks a lot for spotting and reporting these. Will fix as appropriate.
Regards,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-08 06:42 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Jaime Casanova <jaime.casanova@2ndquadrant.com>
1 sibling, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-08 06:42 UTC (permalink / raw)
To: Jaime Casanova <jaime.casanova@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Hi Jaime,
On 2016/11/08 2:15, Jaime Casanova wrote:
> On 28 October 2016 at 02:53, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> I started to review the functionality of this patch, so i applied all
> 9 patches. After that i found this warning, which i guess is because
> it needs a cast.
Thanks a ton for reviewing!
> After that, i tried a case that i think is important: to partition an
> already existing table. Because there is no ALTER TABL SET PARTITION
> or something similar (which i think makes sense because such a command
> would need to create the partitions and move the rows to follow the
> rule that there is no rows in a parent table).
>
> So, what i tried was:
>
> 1) rename original table
> 2) create a new partitioned table with old name
> 3) attach old table as a partition with bounds outside normal bounds
> and no validate
>
> the idea is to start attaching valid partitions and delete and insert
> rows from the invalid one (is there a better way of doing that?), that
> will allow to partition a table easily.
So, step 3 creates a partition that is basically unbounded. From there,
it seems you want to create partitions with proper bounds, moving data
into them as they are created. It seems like a job of some redistribution
command (split partition?) which is currently unsupported.
> So far so good, until i decided points 1 to 3 should happen inside a
> transaction to make things transparent to the user.
>
> Attached is script that shows the failure when trying it:
>
> script 1 (failing_test_1.sql) fails the assert
> "Assert(RelationGetPartitionKey(parentRel) != NULL);" in
> transformAttachPartition() at src/backend/parser/parse_utilcmd.c:3164
Thanks for the test. This test uncovered a bug which I have fixed in my
local repository. Given the fix the above will work, although I see that
it's not the best way to do what you want to do.
> After that i tried the same but with an already partitioned (via
> inheritance) table and got this (i did this first without a
> transaction, doing it with a transaction will show the same failure as
> before):
>
> script 2 (failing_test_2.sql) fails the assert
> "Assert(childrte->relkind == RELKIND_PARTITIONED_TABLE);" in
> expand_inherited_rte_internal() at
> src/backend/optimizer/prep/prepunion.c:1551
This again was an oversight/bug in the patch. It's not supported to
combine old-style inheritance partitioning with the new partitioned
tables. In fact, ATTACH PARTITION prevents adding a regular inheritance
parent as partition. After fixing the bug, you would instead get this error:
alter table prueba attach partition prueba_old for values start
(unbounded, unbounded) end (2008,1) no validate;
ERROR: cannot attach regular inheritance parent as partition
In this case, you should instead create prueba_old partition hierarchy
using the new partitioning commands and then attach the same.
> PS: i don't like the START END syntax but i don't have any ideas
> there... except, there was a reason not to use expressions (like a
> CHECK constraint?)
Expression syntax is too unrestricted. What's to prevent users from using
completely different check constraint expressions from partition to
partition (not that any users deliberately try do that)? With the new
partitioning syntax, you specify the partitioning columns once when
creating the partitioned parent and then specify, using partitioning
method specific syntax, the bounds for every partition of the parent.
That allows to capture the partition metadata in a form that is more
suitable to implement other features related to partitioning.
That said, I think it's always a challenge to come up with a syntax that
is universally acceptable. For example, the recent discussion about
whether to allow inclusive/exclusive to be specified for START and END
bounds of range partitions or always assume inclusive start and exclusive
end [1].
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoaKOycHcVoed%2BF3fk-z6xUOeysQFG6HT%3Doucw76bSMHCQ%40ma...
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-09 11:14 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-09 11:14 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/04 9:16, Robert Haas wrote:
> Apologies if I've made some of these comments before and/or missed
> comments you've made on these topics. The size of this patch set is
> so large that it's hard to keep track of everything.
Thanks for the reviews!
> Re-reviewing 0001:
>
> + indicate which table columns are used as partition key. For example,
>
> s/are used as/are part of the/
Fixed.
>
> + 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.
>
> I think you can leave out "over the table columns".
Done.
> + columns or expressions forms the <firstterm>partitioning key</firstterm>
>
> s/forms/form/
Actually the sentence there is: The parenthesized list of columns or
expressions forms the the <firstterm>partitioning key</firstterm> for the
table
>
> + The table itself is empty. A data row inserted into the table is routed
>
> s/The table/The partitioned table/
Done.
>
> + * Anything mentioned in the expressions. We must ignore the column
> + * references which will count as self-dependency items; in this case,
> + * the depender is the table itself (there is no such thing as partition
> + * key object).
>
> "depender" is not really a word, and the parenthetical note isn't very
> clear. Maybe: We must ignore the column references, which will depend
> on the table itself; there is no separate partition key object.
I see "depender" being used as a variable name, but I guess it's not
appropriate to use the same in comments. In any case, I adopted your text.
>
> + heap_close(pg_partitioned_table, RowExclusiveLock);
>
> It seems like it would be safe to do this right after
> CatalogUpdateIndexes(pg_partitioned_table, tuple), and I'd recommend
> that you do. Not for performance or anything, but just to keep
> related code together.
I see, done.
>
> /*
> * Resolve possibly-defaulted operator class specification
> */
> -static Oid
> +Oid
> GetIndexOpClass(List *opclass, Oid attrType,
>
> Perhaps we should rename this function to ResolveOpClass, since it's
> now going to be used for both indexes and partitioning keys.
Aha, good idea! Done.
> + * Sets *is_expr if attnum is found to be referenced in some partition key
> + * expression.
>
> is_expr doesn't seem quite as clear as, say, used_by_expr or used_in_expr.
>
> Also, the specification for this function doesn't seem to be very
> clear about what this is supposed to do if the same column is both an
> explicit partitioning column and also used in an expression, and the
> code looks like it'll return with *is_expr set based on whichever use
> it encounters first. If that's intended behavior, maybe add a comment
> like: It's possible for a column to be used both directly and as part
> of a partition key expression; if that happens, *is_expr may end up as
> either true or false. That's OK for current uses of this function,
> because *is_expr is only used to tailor the error message text.
OK, I added the note as you suggest.
>
> + if (is_expr)
> + *is_expr = false;
> + if (attnum == partattno)
> + return true;
>
> I think you should adjust this (and the other bit in the same
> function) so that you don't set *is_expr until you're committed to
> returning.
Done.
>
> + index = -1;
> + while ((index = bms_next_member(expr_attrs, index)) > 0)
> + {
> + AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
> +
> + if (attno == attnum)
> + return true;
> + }
>
> How about bms_is_member(expr_attrs, attnum -
> FirstLowInvalidHeapAttributeNumber), instead of looping?
Done that way, thanks.
> + errmsg("cannot reference relation \"%s\"",
> RelationGetRelationName(pkrel)),
> + errdetail("Referencing partitioned tables in foreign
> key constraints is not supported.")));
>
> I think you could get rid of the errdetail and just have the error
> message be "cannot reference partitioned table \"%s\"".
OK, done.
>
> + errmsg("column \"%s\" appears twice in
> partition key", pelem->name),
>
> It could be there three times! How about column \"%s\" appears more
> than once in partition key? (I see that you seem to have adapted this
> from some code in parse_utilcmd.c, which perhaps should also be
> adjusted, but that's a job for another patch.)
Done.
>
> + /*
> + * Strip any top-level COLLATE clause. This ensures that we treat
> + * "x COLLATE y" and "(x COLLATE y)" alike.
> + */
>
> But you don't, right? Unless I am confused, which is possible, the
> latter COLLATE will be ignored, while the former one will set the
> collation to be used in the context of partitioning comparisons.
The code immediately following the comment does in fact strip the
top-level COLLATE clause.
while (IsA(expr, CollateExpr))
expr = (Node *) ((CollateExpr *) expr)->arg;
So that the following two specifications are equivalent which is the intent:
create table p (a text) partition by range (a collate "en_US");
vs.
create table p (a text) partition by range ((a collate "en_US"));
> Re-reviewing 0002:
>
> + if (fout->remoteVersion >= 100000)
> + {
> + PQExpBuffer acl_subquery = createPQExpBuffer();
> + PQExpBuffer racl_subquery = createPQExpBuffer();
> + PQExpBuffer initacl_subquery = createPQExpBuffer();
> + PQExpBuffer initracl_subquery = createPQExpBuffer();
> +
> + PQExpBuffer attacl_subquery = createPQExpBuffer();
> + PQExpBuffer attracl_subquery = createPQExpBuffer();
> + PQExpBuffer attinitacl_subquery = createPQExpBuffer();
> + PQExpBuffer attinitracl_subquery = createPQExpBuffer();
>
> It seems unnecessary to repeat all of this. The only differences
> between the 10000 version and the 9600 version are:
>
> 60,61c60
> < "AS changed_acl, "
> < "CASE WHEN c.relkind = 'P' THEN
> pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
> ---
>> "AS changed_acl "
> 73c72
> < "WHERE c.relkind in ('%c', '%c', '%c', '%c',
> '%c', '%c', '%c') "
> ---
>> "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
> 87,88c86
> < RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
> < RELKIND_PARTITIONED_TABLE);
> ---
>> RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
>
> But none of that is really a problem. Sure, the 'P' case will never
> arise in 9.6, but so what? I'd really like to not keep duplicating
> these increasingly-complex hunks of code if we can find some way to
> avoid that.
We cannot reference pg_catalog.pg_get_partkeydef() in the SQL query that
getTables() sends to pre-10 servers, right? But I suppose we need not
call it in that particular SQL query in the first place.
How about we do it in the following manner in getSchemaData():
if (g_verbose)
write_msg(NULL, "reading partition key information for interesting
tables\n");
getTablePartitionKeyInfo(fout, tblinfo, numTables);
I have implemented the same.
>
> /* 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')");
>
> I think you need to update the comment. "Find inheritance
> information, excluding implicit inheritance via partitioning. We're
> not interested in that case because $REASON."
I just realized that this hunk really belongs in patch 0004. In any case,
I added the explanatory comment like you suggest.
> Re-reviewing 0003:
>
> + <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.
> + </para>
>
> This would, presumably, also be true for inheritance. I think we
> could just leave this out.
Actually, it isn't true for the regular inheritance situation.
create table parent (a int not null);
create table child () inherits (parent);
alter table child alter a drop not null; -- this works (bug?)
vs.
create table p (a int, b int, c int) partition by range (a);
create table p1 partition of p for values start (1) end (10);
alter table p1 alter a drop not null; -- this causes error
Once NOT NULL constraints start using pg_constraint [1] to keep track of
inheritance (which currently is not kept track of), the above illustrated
bug will be fixed. If that patch gets committed, it will have taken care
of the partitioning case as well.
But in the meantime, we can proceed with enforcing inheritance on NOT NULL
constraint for *partitions*, because they only ever have one parent and
hence do not need elaborate coninhcount based scheme, I think. Thoughts?
>
> + as partition of the target table. The partition bound specification must
>
> s/as partition/as a partition/
Fixed.
>
> + correspond to the partitioning method and partitioning key of the target
>
> I think that in most places were are referring to the "partitioning
> method" (with ing) but the "partition key" (without ing). Let's try to
> be consistent.
I'm inclined to switch to "partitioning method" and "partitioning key",
but do you mean just the documentation or throughout? Beside
documentation, I mean source code comments, error messages, etc. I have
assumed throughout.
>
> + table. The table to be attached must have all the columns as the target
> + table and no more; moreover, the column types must also match. Also, it
> + must have all the matching constraints as the target table.
>
> s/all the columns/all of the same columns/
Fixed.
> The second sentence doesn't seem quite grammatical. And why would
> that be true anyway? Partitions can have extra constraints, and if
> they lack constraints that are present on the partitioned table, those
> constraints will be added and validated, right?
It's true that partitions can have extra constraints which don't affect
the discussion. What I meant to say with the second sentence is that any
check constraints defined on the parent table (being referred to as the
target table) must be present in the table being attached. It's the same
rule as for regular inheritance. I didn't change how that works in the
partitioning case. So no, check constraints of the parent table that are
missing in the table being attached are not added and validated as part of
the attach partition command processing.
I did change the second sentence to: Also, it must have all the
<literal>NOT NULL</literal> and <literal>CHECK</literal> constraints
present in the target table.
>
> + A full table scan is performed on the table being attached to check that
> + existing rows in the table are within the specified partition bound.
> + If it is known in advance that no partition bound violating rows are
> + present in the table, the above scan can be skipped by specifying the
> + <literal>NO VALIDATE</> option.
> + Default behavior is to perform the scan, as if the
> <literal>VALIDATE</literal>
> + option were specified.
>
> I don't think it's OK to allow the partition to be added if it
> contains rows that might not be valid. We are generally vary wary
> about adding options that can cause data integrity failures and I
> think we shouldn't start here, either. On the other hand, it's also
> not desirable for adding a partition to take O(n) time in all cases.
> So what would be nice is if the new partition could self-certify that
> contains no problematic rows by having a constraint that matches the
> new partitioning constraint. Then we can skip the scan if that
> constraint is already present.
I agree that NO VALIDATE is undesirable and it would be better if we could
get rid of the same. I want to clarify one thing though: what does it
mean for the new partition to have a constraint that *matches* the new
partitioning constraint? Does it mean the new partition's constraint
*implies* the partitioning constraint? Or as certified by equal()?
>
> + inherited columns. One can also specify table constraints, in addition
>
> Delete comma.
Done.
>
> + to those inherited from the parent. If a check constraint with the name
> + matching one of the parent's constraint is specified, it is merged with
> + the latter, provided the specified condition is same.
>
> Doesn't that effectively delete the merged constraint?
>
> Suggest: "If the parent already has a check constraint with the same
> name as a constraint specified for the child, the conditions must be
> the same."
>
> +) FOR VALUES IN ('los angeles', 'san fransisco');
>
> That's not you you spell San Francisco.
Oops. I changed the example such that specified values are properly
capitalized.
<programlisting>
CREATE TABLE cities (
name text not null,
population int,
) PARTITION BY LIST (initcap(name));
</programlisting></para>
<programlisting>
CREATE TABLE cities_west
PARTITION OF cities (
CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco');
</programlisting></para>
Previously, the key was: PARTITION BY LIST (lower(name))
> + Create partition of a list partitioned table that itself is further
> + partitioned and then create its partition:
>
> s/itself is/is itself/
> s/then create its partition/then add a partition to it/
Fixed.
>
> + if (!is_local || con->coninhcount == 0)
> + con->coninhcount++;
>
> I would think that you could get rid of the "if" and just say
> con->coninhcount = 1.
That's better. So:
/*
* In case of partitions, an inherited constraint must be
* inherited only once since it cannot have multiple parents and
* it is never considered local.
*/
if (rel->rd_rel->relispartition)
{
con->coninhcount = 1;
con->conislocal = false;
}
The above is enforced in both MergeWithExistingConstraint() and
MergeConstraintsIntoExisting().
> It seems to me (and perhaps the comment could
> say this) that for a partitioned table, we can simplify the handling
> of coninhcount and conislocal. Since partitioning hierarchies don't
> allow multiple inheritance, any inherited constraint must be inherited
> exactly once. Since a partition cannot have inheritance children --
> except by being partitioned itself -- there is no point in tracking
> conislocal, so we always store it as false.
Agreed.
> +
> +void
> +StorePartitionBound(Relation rel, Node *bound)
>
> Header comment, please!
Sorry about that, added.
>
> + (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
> + &isnull);
> + Assert(isnull);
>
> We try not to do unnecessary work in non-assert-enabled builds solely
> for the benefit of assert-enabled builds. We also try not to end up
> with variables that are only used in assert-enabled builds but not
> marked PG_USED_FOR_ASSERTS_ONLY, because those tend to cause compiler
> warnings. I'm not sure an compiler would be smart enough to warn
> about this, but I suggest adding an #ifdef USE_ASSERT_CHECKING with a
> block inside where the offending variable is declared. Actually, I
> think you need to move a bit more code. Hmm. Something like:
>
> #ifdef USE_ASSERT_CHECKING
> {
> Form_pg_class classForm;
> bool isnull;
>
> classForm = (Form_pg_class) GETSTRUCT(tuple);
> Assert(!classForm->relispartition);
>
> (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
> &isnull);
> Assert(isnull);
> }
> #endif
Done this way, thanks.
>
> + * are same in common cases, of which we only store one.
>
> "and we only store one of them."
Done.
>
> + /*
> + * Never put a
> null into the values array, flag
> + * instead for
> the code further down below where
> + * we
> construct the actual relcache struct.
> + */
> +
> found_null_partition = true;
> +
> null_partition_index = i;
>
> How about we elog(ERROR, ...) if found_null_partition is already set?
Makes sense. However, let me mention here that duplicates either within
one partition's list or across the partitions are not possible. That's
because in case of the former, we de-duplicate before storing the list
into the catalog and the latter would simply be an overlap error. Could
this be made an Assert() then?
>
> + foreach(cell, non_null_values)
> + {
> + PartitionListValue *src =
> lfirst(cell);
> +
> + all_values[i] = (PartitionListValue *)
> +
> palloc(sizeof(PartitionListValue));
> + all_values[i]->value =
> datumCopy(src->value,
> +
> key->parttypbyval[0],
> +
> key->parttyplen[0]);
> + all_values[i]->index = src->index;
> + i++;
> + }
>
> Why do we need to datumCopy() here when we just did that in the
> previous loop? Why did the previous loop need to datumCopy(), anyway?
> Can't we just pass the same datums around? I understand that you
> need to copy the data into the correct context at the end, but doing
> two copies prior to that seems excessive.
Agreed. Now the datumCopying() happens only once when copying to the
relcache context.
> +get_leaf_partition_oids(Oid relid, int lockmode)
>
> Instead of implementing this, can you just use find_all_inheritors()?
OK, got rid of get_leaf_partition_oids().
>
> + /*
> + * Is lower_val = upper_val?
> + */
> + if (lower_val && upper_val)
>
> So, that comment does not actually match that code. That if-test is
> just checking whether both bounds are finite. What I think you should
> be explaining here is that if lower_val and upper_val are both
> non-infinite, and if the happen to be equal, we want to emit an
> equality test rather than a >= test plus a <= test because $REASON.
OK, I have added some explanatory comments around this code.
>
> + operoid = get_opfamily_member(key->partopfamily[col],
> +
> key->parttypid[col],
> +
> key->parttypid[col],
> + strategy);
> + if (!OidIsValid(operoid))
> + {
> + operoid = get_opfamily_member(key->partopfamily[col],
> +
> key->partopcintype[col],
> +
> key->partopcintype[col],
> +
> strategy);
> + *need_relabel = true;
> + }
>
> There's a comment explaining THAT you do this ("Use either the column
> type as the operator datatype or opclass's declared input type.") but,
> to repeat a complaint that I've made often, nothing explaining why.
> In this particular case, what's not clear - in my opinion - is why you
> need to try two different possibilities, and why at least one of those
> possibilities is guaranteed to work. I gather that if the opfamily
> doesn't contain an operator for the actual type of the partitioning
> column, you think it will certainly contain one for the input type of
> the operator class (which seems right), and that the input type of the
> operator class will be binary-compatible with the type of the
> partitioning column (which is less-obviously true, and needs some
> explanation).
Sorry, it is indeed not clear why the code is the way it it. I expanded
the comment explaining why (borrowing from what you wrote above).
>
> I also think that this function should elog(ERROR, ...) if by any
> chance the second get_opfamily_member() call also fails. Otherwise the
> error might occur quite a bit downstream and be hard to understand.
OK, added an elog(ERROR, ...) after the second get_opfamily_member() call.
>
> + ReleaseSysCache(tuple);
> + heap_close(parent, AccessShareLock);
>
> I think you had better not release the lock here - i.e. pass NoLock.
> We don't release locks on tables until transaction commit, except for
> catalog tables. This also comes up in, at least,
> transformPartitionOf().
Ah, fixed.
> + PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
> + PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
> + PartitionKey key = (PartitionKey) arg;
> +
> + return partition_rbound_cmp(key, b1, b2);
>
> Whitespace.
Fixed.
>
> + * as partition and schema consists of columns definitions corresponding
>
> the schema consists
>
> - if (recurse)
> + /* Force inheritance recursion, if partitioned table. */
> + if (recurse || rel->rd_rel->relkind ==
> RELKIND_PARTITIONED_TABLE)
>
> I would instead error out if ONLY is specified for a partitioned table.
I forgot this instance of forced-recursion-if-partitioned, fixed. I was
not quite sure what the error message would say; how about:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("must truncate child tables too")));
> + /*
> + * If the table is source table of ATTACH PARTITION
> command, following
> + * check is unnecessary.
> + */
>
> As usual, comment should say WHY.
OK, expanded the comment explaining why.
>
> + if (partqualstate && !ExecQual(partqualstate,
> econtext, true))
> + ereport(ERROR,
> +
> (errcode(ERRCODE_CHECK_VIOLATION),
> + errmsg("child table
> contains a row violating partition bound specification")));
>
> Why not mimic the existing phrasing? "partition constraint is
> violated by some row"
Agreed, done.
>
> What happens if you try to attach a table as a partition of itself or
> one of its ancestors?
The latter fails with "already a partition" error.
The former case was not being handled at all which has now been fixed.
ATExecAddInherit() prevents that case as an instance of preventing the
circularity of inheritance. It says: ERROR: circular inheritance not
allowed. And then: DETAIL: "rel" is already a child of "rel".
Should ATExecAttachPartition() use the same trick and keep using the same
message(s)? The above detail message doesn't quite sound appropriate when
one tries to attach a table as partition of itself.
>
> - errmsg("column \"%s\" in child table
> must be marked NOT NULL",
> - attributeName)));
> + errmsg("column \"%s\"
> in child table must be marked NOT NULL",
> +
> attributeName)));
>
> Whitespace-only hunk; revert. You cannot fight the power of pgindent.
Oops, fixed.
>
> + errmsg("cannot attach table that is a
> inheritance child as partition")));
>
> an inheritance child
>
> + errmsg("cannot attach a temporary relation of another
> session as partition ")));
>
> Extra space.
Fixed.
>
> + errdetail("Table being
> attached should contain only the columns"
> + " present
> in parent.")));
>
> Suggest: "New partition should contain only..."
>
> Also, don't break error messages into multiple strings. Make it one
> long string and let pgindent deal.
Done.
> + | FOR VALUES START '(' range_datum_list ')' lb_inc
> + END_P '('
> range_datum_list ')' ub_inc
>
> Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
> idiomatic than START and END?
It would actually. Should I go ahead and change to FROM (...) TO (...)?
Related to range partitioning, should we finalize on inclusive START/FROM
and exclusive END/TO preventing explicit specification of the inclusivity?
> +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);
> +
>
> If there's anyway that we might do heap_openrv() on the same RangeVar
> at multiple places in the code, it presents security and integrity
> hazards because the referent of that RangeVar might change in the
> meantime. I suspect there is such a risk here - won't we need to open
> the relation again when we actually want to do the operation?
>
> Also, we should never acquire a lower-level lock on a relation and
> then, further downstream, acquire a higher-level lock on the same
> object. To do so creates a deadlock hazard. That seems like it might
> be a problem here, too.
I think I have modified things so that the concerns you express are taken
care of. In particular, I got rid of transformPartitionOf() altogether,
moving part of its responsibilities to MergeAttributes() where the parent
table is locked anyway and calling transformPartitionBound from
DefineRelation() (we needed access to the parent's PartitionKey for that).
>
> + /*if (ldatum->infinite && rdatum->infinite)
> + ereport(ERROR,
> +
> (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
> + errmsg("both START
> and END datum for column \"%s\" cannot be UNBOUNDED",
> + colname),
> +
> parser_errposition(cxt->pstate, rdatum->location)));*/
>
> Commented-out code is bad.
Oops, removed.
>
> + if (list_length(spec->lowerdatums) > partnatts)
> + ereport(ERROR,
> +
> (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
> + errmsg("START has more values
> than the length of the partition key"),
> +
> parser_errposition(cxt->pstate,
> +
> exprLocation(list_nth(spec->lowerdatums,
> +
> list_length(spec->lowerdatums) - 1)))));
> + else if (list_length(spec->lowerdatums) < partnatts)
> + ereport(ERROR,
> +
> (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
> + errmsg("START has fewer
> values than the length of the partition key"),
> +
> parser_errposition(cxt->pstate,
> +
> exprLocation(list_nth(spec->lowerdatums,
> +
> list_length(spec->lowerdatums) - 1)))));
>
> It would be worth looking for a way to unify these cases. Like "START
> must specify exactly one value per partitioning column".
Agreed. The code is trying to generate too specific error messages.
> + * Same oids? No mind order - in the list case, it
> matches the order
> + * in which partition oids are returned by a
> pg_inherits scan, whereas
> + * in the range case, they are in order of ranges of individual
> + * partitions. XXX - is the former unsafe?
>
> Probably. It likely depends on the physical ordering of tuples in the
> table, which can change.
This comment is outdated. RelationBuildPartitionDesc() now always puts
the OIDs into the array in a canonical order (in both list and range
cases) using. I have updated the comment.
>
> + * BoundCollection encapsulates a set of partition bounds of either physical
> + * or logical relations. It is associated with a partitioned relation of
> + * which the aforementioned relations are partitions.
>
> "physical or logical relations" is unfamiliar terminology.
What I am trying to say there is that one can associate a BoundCollection
with either an actual partitioned table or with a transient partitioned
relation, say, a partitioned joinrel. In case of the former, the
BoundCollection is part of the table's partition descriptor. I can see
though that "logical/physical relations" terminology is misleading at
best. I rewrote the comment:
/*
* BoundCollection encapsulates a set of partition bounds. It is usually
* associated with partitioned tables as part of its partition descriptor.
*
* The internal structure is opaque outside partition.c.
*/
typedef struct BoundCollectionData *BoundCollection;
Attached updated patches take care of the above comments and few other
fixes. There are still a few items I have not addressed right away:
- Remove NO VALIDATE clause in ATTACH PARTITION and instead rely on the
new partition's constraints to skip the validation scan
- Remove the syntax to specify inclusivity of each of START and END bounds
of range partitions and instead assume inclusive START and exclusive END
Also, what used to be "[PATCH 5/9] Refactor optimizer's inheritance set
expansion code" is no longer included in the set. I think we can go back
to that topic later as I mentioned last week [2].
Thanks,
Amit
[1] https://www.postgresql.org/message-id/16589.1465997664%40sss.pgh.pa.us
[2]
https://www.postgresql.org/message-id/0a299fbf-e5a1-dc3d-eb5e-11d3601eac16%40lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-11.patch (114.5K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-11.patch)
download | inline diff:
From 9d53813d2f1e8ac1ab0d6f57c56a40cc00d55a87 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.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 165 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 24 +-
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 | 450 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 ++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 10 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 268 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 3 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 29 ++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 68 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 43 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 29 ++
src/test/regress/sql/create_table.sql | 143 +++++++++
51 files changed, 1947 insertions(+), 54 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..255fdde 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partitioning key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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 partitioning 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 part of the partitioning key.
+ For example, a value of <literal>1 3</literal> would mean that the
+ first and the third table columns make up the partitioning key. A zero
+ in this array indicates that the corresponding partitioning key column
+ is an expression, 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 partitioning 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>partcollation</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 partitioning key, this contains the OID of
+ the collation to use for partitioning.
+ </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 partitioning key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partattrs</>. Null if all partitioning 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..5b2a867 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,41 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partitioning key</firstterm>
+ for the table. When using range partitioning, the partitioning key can
+ include multiple columns or expressions, but for list partitioning, the
+ partitioning key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions),
+ which are created using separate <literal>CREATE TABLE</> commands.
+ The partitioned table is itself empty. A data row inserted into the
+ table is routed to a partition based on the value of columns or
+ expressions in the partitioning key. If no existing partition matches
+ the values in the new row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..232a432 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -3018,3 +3041,137 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * StorePartitionKey
+ * Store information about the partitioning key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition attribute numbers, opclass OIDs into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references, which will depend on the table itself; there is no
+ * separate partitioning key object.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ /*
+ * We must invalidate the relcache so that the next
+ * CommandCounterIncrement() will cause the same to be rebuilt using the
+ * information in just created catalog entry.
+ */
+ CacheInvalidateRelcache(rel);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partitioning key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b4140eb..0ef590a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..96358bb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Identify the opclass to use.
*/
- classOidP[attn] = GetIndexOpClass(attribute->opclass,
- atttype,
- accessMethodName,
- accessMethodId);
+ classOidP[attn] = ResolveOpClass(attribute->opclass,
+ atttype,
+ accessMethodName,
+ accessMethodId);
/*
* Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partitioning key definition.
*/
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..82588a8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
rel = relation_open(relationId, AccessExclusiveLock);
/*
+ * Process the partitioning specification (if any) and store the
+ * partitioning key information into the catalog.
+ */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
+ /*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression. It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false. That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (attnum == partattno)
+ {
+ if (used_in_expr)
+ *used_in_expr = false;
+ return true;
+ }
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+
+ /* Find all attributes referenced */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ {
+ if (used_in_expr)
+ *used_in_expr = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5842,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 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in the partitioning 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 partitioning key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partitioning key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference partitioned table \"%s\"",
+ RelationGetRelationName(pkrel))));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in the partitioning key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partitioning key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partitioning key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partitioning key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partitioning strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears more than once in partitioning key",
+ pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ /* Simple attribute reference */
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partitioning 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 partitioning key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+
+ /* Note that whole-row references can't happen here; see below */
+ }
+ else
+ {
+ /* Expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ Bitmapset *expr_attrs = NULL;
+
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partitioning key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partitioning key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+
+ /* Cannot have expressions containing whole-row references */
+ pull_varattnos(expr, 1, &expr_attrs);
+ if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partitioning key expressions cannot contain whole-row references")));
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = ResolveOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
case T_TriggerTransition:
retval = _equalTriggerTransition(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition:
_outTriggerTransition(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..76c0793 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -545,6 +547,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partitioning key specification */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..0627256 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partitioning key expression");
+ else
+ err = _("grouping operations are not allowed in partitioning key expression");
+
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("window functions are not allowed in partitioning key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..3facde2 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partitioning key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..41b85c3 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partitioning key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..eaf809a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partitioning key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partitioning key information. That means no complicated
+ * logic to free individual elements whenever the relcache entry is flushed -
+ * just delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..a1a62bb
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partitioning key */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of columns in the
+ * partitioning key */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* list of expressions in the partitioning
+ * key; one item for each zero entry in
+ * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9b600a5..a04dbeb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partitioning key
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partitioning key specification
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy; /* partitioning strategy ('list' or 'range') */
+ List *partParams; /* List of PartitionElems */
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1768,6 +1796,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..6e1ec82 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 partitioning 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 c867ebb..e751491 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Information about the partitioning key of a relation
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partitioning key */
+ AttrNumber *partattrs; /* attribute numbers of columns in the
+ * partitioning key */
+ List *partexprs; /* list of expressions in the partitioning
+ * key, or NIL */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation per attribute */
+ Oid *partcollation;
+
+ /* Type information per attribute */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partitioning 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 */
@@ -533,6 +565,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..c39fac5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
c4 | integer | | |
DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR: cannot drop column named in partitioning key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partitioning key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR: cannot drop column referenced in partitioning key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR: cannot alter type of column referenced in partitioning key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR: cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR: cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..0d70cc9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+ERROR: column "a" appears more than once in partitioning key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: set-returning functions are not allowed in partitioning key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partitioning key expression
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partitioning key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partitioning key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partitioning key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partitioning key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partitioning key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partitioning key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partitioning key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+ERROR: partitioning key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table partitioned depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR: cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-11.patch (23.9K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-11.patch)
download | inline diff:
From 2964345f52bfb8edf98b5eeaaab8781d5e1c669f 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 | 159 ++++++++++++++++++++++++++++
src/bin/pg_dump/common.c | 4 +
src/bin/pg_dump/pg_dump.c | 66 +++++++++++-
src/bin/pg_dump/pg_dump.h | 2 +
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 | 20 ++++-
src/test/regress/sql/create_table.sql | 6 +-
10 files changed, 301 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..319531c 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_table.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,
@@ -1412,6 +1414,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partitioning key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partitioning key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ oidvector *partcollation;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partitioning key of %u", relid);
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass and partcollation the hard way */
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partcollation, &isnull);
+ Assert(!isnull);
+ partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+ /*
+ * Get the 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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relid), relid);
+
+ initStringInfo(&buf);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+ Oid keycolcollation;
+ Oid partcoll;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple attribute reference */
+ char *attname;
+ int32 keycoltypmod;
+
+ attname = get_relid_attribute_name(relid, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ get_atttypetypmodcoll(relid, attnum,
+ &keycoltype, &keycoltypmod,
+ &keycolcollation);
+ }
+ else
+ {
+ /* 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, str);
+ keycoltype = exprType(partkey);
+ keycolcollation = exprCollation(partkey);
+ }
+
+ /* Add collation, if not default for column */
+ partcoll = partcollation->values[keyno];
+ if (OidIsValid(partcoll) && partcoll != keycolcollation)
+ appendStringInfo(&buf, " COLLATE %s",
+ generate_collation_name((partcoll)));
+
+ /* 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/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c12c490 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading policies\n");
getPolicies(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading partitioning key information for interesting tables\n");
+ getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..c393264 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4946,7 +4950,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,
@@ -4960,7 +4964,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5488,7 +5493,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6897,6 +6904,47 @@ getTransforms(Archive *fout, int *numTransforms)
}
/*
+ * getTablePartitionKeyInfo -
+ * for each interesting partitioned table, read information about its
+ * partitioning key
+ *
+ * modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ int i,
+ ntups;
+ PGresult *res;
+
+ /* No partitioned tables before 10 */
+ if (fout->remoteVersion < 100000)
+ return;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &(tblinfo[i]);
+
+ /* Only partitioned tables have partitioning key */
+ if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ resetPQExpBuffer(q);
+ appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+ Assert(ntups == 1);
+ tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+ }
+}
+
+/*
* getTableAttrs -
* for each interesting table, read info about its attributes
* (names, types, default values, CHECK constraints, etc)
@@ -14249,6 +14297,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -14309,6 +14360,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14379,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -14544,6 +14597,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 a60cf95..a75e79c 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; /* partitioning key definition */
/*
* Stuff computed only for dumpable tables.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
#endif /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..6d44378 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1566,8 +1568,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)");
}
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,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')
{
headers[cols++] = gettext_noop("Collation");
headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,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");
}
@@ -1748,7 +1758,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);
@@ -1756,14 +1766,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 (tableinfo.relkind == 'P')
+ {
+ /* Get the partitioning 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, _("Partitioning key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1902,7 +1931,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;
@@ -2461,7 +2490,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;
@@ -2665,7 +2694,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
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3048,7 +3079,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 b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,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 */
@@ -483,7 +483,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 */
@@ -513,7 +513,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 17ec71d..5e49475 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,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("partitioning 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 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,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 0d70cc9..bec5655 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
ERROR: cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+ Table "public.partitioned"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+ d | text | | |
+Partitioning key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+ Table "public.partitioned2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partitioning key: LIST ((a + 1))
+
DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-11.patch (202.7K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-11.patch)
download | inline diff:
From 7b1324c79b421bb08d6800e10a2ca9a2d2f9f9d0 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.
pg_class gets new fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 100 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 26 +
doc/src/sgml/ref/create_table.sgml | 92 ++-
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 105 ++-
src/backend/catalog/partition.c | 1685 ++++++++++++++++++++++++++++
src/backend/commands/createas.c | 2 +-
src/backend/commands/sequence.c | 2 +-
src/backend/commands/tablecmds.c | 897 +++++++++++++---
src/backend/commands/typecmds.c | 3 +-
src/backend/commands/view.c | 3 +-
src/backend/nodes/copyfuncs.c | 50 +
src/backend/nodes/equalfuncs.c | 44 +
src/backend/nodes/nodeFuncs.c | 6 +
src/backend/nodes/outfuncs.c | 29 +
src/backend/nodes/readfuncs.c | 36 +
src/backend/parser/gram.y | 235 ++++-
src/backend/parser/parse_utilcmd.c | 252 +++++-
src/backend/tcop/utility.c | 6 +-
src/backend/utils/cache/relcache.c | 93 ++-
src/include/catalog/heap.h | 1 +
src/include/catalog/partition.h | 54 +
src/include/catalog/pg_class.h | 22 +-
src/include/commands/tablecmds.h | 2 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 52 +-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_utilcmd.h | 2 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 237 ++++
src/test/regress/expected/create_table.out | 190 ++++
src/test/regress/sql/alter_table.sql | 198 ++++
src/test/regress/sql/create_table.sql | 153 +++
34 files changed, 4458 insertions(+), 165 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 255fdde..b1ddb12 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..9ae16ac 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,47 @@ 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 table (which might itself be partitioned)
+ as a partition of the target table. The partition bound specification
+ must correspond to the partitioning method and partitioning key of the
+ target table. The table to be attached must have all the same columns
+ as the target table and no more; moreover, the column types must also
+ match. Also, it must have all the <literal>NOT NULL</literal> and
+ <literal>CHECK</literal> constraints present in the target table.
+ If some <literal>CHECK</literal> constraint of the table being attached
+ is marked <literal>NO INHERIT</literal>, the command will fail; such
+ constraints must be recreated without the <literal>NO INHERIT</literal>
+ clause. Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+ and <literal>FOREIGN KEY</literal> constraints are not considered.
+ </para>
+
+ <para>
+ A full table scan is performed on the table being attached to check that
+ existing rows in the table are within the specified partition bound.
+ If it is known in advance that no partition bound violating rows are
+ present in the table, the above scan can be skipped by specifying the
+ <literal>NO VALIDATE</> option.
+ Default behavior is to perform the scan, as if the <literal>VALIDATE</literal>
+ option were specified.
+ </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, but no longer has any
+ ties to the table from which it was detached.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -721,8 +769,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>.
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.
+ To add the table as a new child of a parent table, or as a new partition
+ of a partitioned table, you must own the parent table as well.
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
@@ -938,6 +986,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 or to 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>
@@ -978,6 +1044,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 specified.
+ </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.
@@ -1047,6 +1119,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited.
+ A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+ partitioned table, because all partitions of a table must have the same
+ columns as the partitioning root.
</para>
<para>
@@ -1233,6 +1308,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..f05f212 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,12 @@ 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.
+ </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 +329,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</>, as a 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 5b2a867..e3a434c 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | START ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] END ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
<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>] [, ... ] ) ]
@@ -232,6 +247,50 @@ 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.
+ </para>
+
+ <para>
+ The partition bound specification must correspond to the partitioning
+ method and partitioning key of the parent table, and must not overlap
+ with any existing partition of that 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.
+ Defaults and constraints can optionally be specified for each of the
+ inherited columns. One can also specify table constraints in addition
+ to those inherited from the parent. If a check constraint with the name
+ matching one of the parent's constraint is specified, it is merged with
+ the latter, provided the specified condition is same.
+ </para>
+
+ <para>
+ Rows inserted into a partitioned table will be automatically routed to
+ the correct partition. If no suitable partition exists, an error will
+ occur.
+ </para>
+
+ <para>
+ A partition must have the same column names and types as the table of
+ which it is a partition. Therefore, modifications to the column names
+ or types of the partitioned table will automatically propagate to all
+ children, as will operations such as TRUNCATE which normally affect a
+ table and all of its inheritance children. It is also possible to
+ TRUNCATE a partition individually, just as for an inheritance child.
+ Dropping a partition directly using <literal>DROP TABLE</literal>
+ will fail; it must be <firstterm>detached</> from the parent first.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
CREATE TABLE cities (
name text not null,
population int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(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 Francisco');
+</programlisting></para>
+
+ <para>
+ Create partition of a list partitioned table that is itself further
+ partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+ PARTITION OF cities (
+ CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') 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 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 232a432..062841b 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"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ /* relpartbound is set by updating this tuple, if necessary */
+ nulls[Anum_pg_class_relpartbound - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ /* relispartition is always set by updating this tuple later */
+ new_rel_reltup->relispartition = false;
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
heap_drop_with_catalog(Oid relid)
{
Relation rel;
+ Oid parentOid;
+ Relation parent = NULL;
/*
* Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
rel = relation_open(relid, AccessExclusiveLock);
/*
+ * If the relation is a partition, we must grab exclusive lock on its
+ * parent because we need to update its partition descriptor. We must
+ * take a table lock strong enough to prevent all queries on the parent
+ * from proceeding until we commit and send out a shared-cache-inval
+ * notice that will make them update their partition descriptor.
+ * Sometimes, doing this is cycles spent uselessly, especially if the
+ * parent will be dropped as part of the same command anyway.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ parentOid = get_partition_parent(relid);
+ parent = heap_open(parentOid, AccessExclusiveLock);
+ }
+
+ /*
* There can no longer be anyone *else* touching the relation, but we
* might still have open queries or cursors, or pending trigger events, in
* our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
* delete relation tuple
*/
DeleteRelationTuple(relid);
+
+ if (parent)
+ {
+ CacheInvalidateRelcache(parent);
+ heap_close(parent, NoLock); /* keep the lock */
+ }
}
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
* definition) then interpret addition of a local constraint as a
* legal merge. This allows ALTER ADD CONSTRAINT on parent and
* child tables to be given in either order with same end state.
+ * However if the relation is a partition, all inherited
+ * constraints are always non-local, including those that were
+ * merged.
*/
- if (is_local && !con->conislocal)
+ if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
allow_merge = true;
if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
tup = heap_copytuple(tup);
con = (Form_pg_constraint) GETSTRUCT(tup);
- if (is_local)
- con->conislocal = true;
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ con->coninhcount = 1;
+ con->conislocal = false;
+ }
else
- con->coninhcount++;
+ {
+ if (is_local)
+ con->conislocal = true;
+ else
+ con->coninhcount++;
+ }
+
if (is_no_inherit)
{
Assert(is_local);
@@ -3175,3 +3223,52 @@ RemovePartitionKeyByRelId(Oid relid)
ReleaseSysCache(tuple);
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * StorePartitionBound
+ * Update pg_class tuple of rel to store the partition bound and set
+ * relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+ Relation classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+ {
+ Form_pg_class classForm;
+ bool isnull;
+
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(!classForm->relispartition);
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+ }
+#endif
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..3ae8cf6
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1685 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; has
+ * same length as the values array above */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ int index;
+ Datum *datums; /* range bound datums */
+ bool *infinite; /* infinite flag for each datum */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, and we only store one of them.
+ */
+typedef struct PartitionRangeInfo
+{
+ PartitionRangeBound **bounds;
+ int nbounds;
+ int *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical)
+ */
+typedef struct BoundCollectionData
+{
+ char strategy; /* list or range bounds? */
+
+ union
+ {
+ struct PartitionListInfo lists;
+ struct PartitionRangeInfo ranges;
+ } bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+ Datum value;
+ int index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums,
+ bool inclusive, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+ PartitionRangeBound *,
+ void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *inhoids,
+ *partoids;
+ Oid *oids = NULL;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ PartitionListValue **all_values = NULL;
+ int all_values_count = 0;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ PartitionRangeBound **distinct_bounds = NULL;
+ int num_distinct_bounds = 0;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ partoids = NIL;
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+
+ /*
+ * It is possible that the pg_class tuple of a partition has not been
+ * updated yet to set its relpartbound field. The only case where
+ * this happens is when we open the parent relation to check using its
+ * partition descriptor that a new partition's bound does not overlap
+ * some existing partition. XXX - perhaps it would be better if we
+ * did not need to use the parent relation desc in that case, because
+ * the following looks kind of ugly.
+ */
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+ {
+ ReleaseSysCache(tuple);
+ continue;
+ }
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ partoids = lappend_oid(partoids, inhrelid);
+ }
+
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc(nparts * sizeof(Oid));
+ i = 0;
+ foreach (cell, partoids)
+ oids[i++] = lfirst_oid(cell);
+
+ /* Convert from node to the internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ List *non_null_values = NIL;
+
+ /*
+ * Create a unified list of non-null values across all
+ * partitions.
+ */
+ i = 0;
+ found_null_partition = false;
+ null_partition_index = -1;
+ foreach(cell, boundspecs)
+ {
+ ListCell *c;
+ PartitionBoundSpec *spec = lfirst(cell);
+
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ foreach (c, spec->listdatums)
+ {
+ Const *val = lfirst(c);
+ PartitionListValue *list_value = NULL;
+
+ if (!val->constisnull)
+ {
+ list_value = (PartitionListValue *)
+ palloc0(sizeof(PartitionListValue));
+ list_value->index = i;
+ list_value->value = val->constvalue;
+ }
+ else
+ {
+ /*
+ * Never put a null into the values array, flag
+ * instead for the code further down below where
+ * we construct the actual relcache struct.
+ */
+ if (found_null_partition)
+ elog(ERROR, "found null more than once");
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+
+ if (list_value)
+ non_null_values = lappend(non_null_values,
+ list_value);
+ }
+
+ i++;
+ }
+
+ all_values_count = list_length(non_null_values);
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (PartitionListValue **)
+ palloc(all_values_count *
+ sizeof(PartitionListValue *));
+ i = 0;
+ foreach(cell, non_null_values)
+ {
+ PartitionListValue *src = lfirst(cell);
+
+ all_values[i] = (PartitionListValue *)
+ palloc(sizeof(PartitionListValue));
+ all_values[i]->value = src->value;
+ all_values[i]->index = src->index;
+ i++;
+ }
+
+ qsort_arg(all_values, all_values_count,
+ sizeof(PartitionListValue *),
+ qsort_partition_list_value_cmp,
+ (void *) key);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ int j, k;
+ PartitionRangeBound **all_bounds,
+ *prev;
+
+ all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+ sizeof(PartitionRangeBound *));
+
+ /*
+ * Create a unified list of range bounds across all the
+ * partitions.
+ */
+ i = j = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundSpec *spec = lfirst(cell);
+ PartitionRangeBound *lower, *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, i,
+ spec->lowerdatums,
+ spec->lowerinc, true);
+ /*
+ * In most cases, upper bound of the i'th partition is
+ * equal in *value* to the lower bound of the (i+1)'th
+ * partition, execpt they have different inclusivity.
+ * We want to keep only the former in the final list of
+ * bounds by eliminating the latter. To be able to equate
+ * the two, we flip the inclusivity flag of the latter,
+ * so that it matches the former's flag value in exactly
+ * the cases where the elimination must occur.
+ */
+ lower->inclusive = !lower->inclusive;
+
+ upper = make_one_range_bound(key, i,
+ spec->upperdatums,
+ spec->upperinc, false);
+ all_bounds[j] = lower;
+ all_bounds[j+1] = upper;
+ j += 2;
+ i++;
+ }
+ Assert(j == 2 * nparts);
+
+ /* Sort all the bounds in ascending order */
+ qsort_arg(all_bounds, 2 * nparts,
+ sizeof(PartitionRangeBound *),
+ qsort_partition_rbound_cmp,
+ (void *) key);
+ /*
+ * Count the number of distinct bounds to allocate an array
+ * of that size.
+ */
+ num_distinct_bounds = 0;
+ prev = NULL;
+ for (i = 0; i < j; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ /*
+ * Count the current bound if it is distinct from the
+ * previous one.
+ */
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ num_distinct_bounds++;
+
+ prev = cur;
+ }
+
+ /*
+ * Finally save them in an array from where they will be
+ * copied into the relcache.
+ */
+ distinct_bounds = (PartitionRangeBound **)
+ palloc0(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ k = 0;
+ prev = NULL;
+ for (i = 0; i < j; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ distinct_bounds[k++] = cur;
+
+ /*
+ * If it's a lower bound, flip the inclusive flag to its
+ * original value.
+ */
+ if (cur->lower)
+ cur->inclusive = !cur->inclusive;
+
+ prev = cur;
+ }
+ Assert(k == num_distinct_bounds);
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ int *mapping;
+ int next_index = 0;
+
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->boundinfo = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ result->boundinfo->strategy = key->strategy;
+
+ /* Allocate and initialize with invalid mapping values */
+ mapping = (int *) palloc(sizeof(int) * nparts);
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionListInfo listinfo;
+
+ listinfo.nvalues = all_values_count;
+ listinfo.has_null = found_null_partition;
+ listinfo.values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo.indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they match for any two list partitioned tables
+ * with same number of partitions and same lists per partition.
+ * One way to achieve this is to assign indexes in the same
+ * order as the least values of the individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo.values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo.indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo.null_index = mapping[null_partition_index];
+ else
+ listinfo.null_index = -1;
+
+ result->boundinfo->bounds.lists = listinfo;
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeInfo rangeinfo;
+
+ rangeinfo.bounds = (PartitionRangeBound **)
+ palloc(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ rangeinfo.indexes = (int *)
+ palloc((num_distinct_bounds + 1) *
+ sizeof(int));
+
+ for (i = 0; i < num_distinct_bounds; i++)
+ {
+ rangeinfo.bounds[i] =
+ copy_range_bound(key, distinct_bounds[i]);
+
+ /* There is no mapping for invalid indexes (ie, -1) */
+ if (rangeinfo.bounds[i]->lower)
+ rangeinfo.indexes[i] = -1;
+ else
+ {
+ int orig_index = rangeinfo.bounds[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[orig_index] == -1)
+ mapping[orig_index] = next_index++;
+
+ rangeinfo.indexes[i] = mapping[orig_index];
+ }
+ }
+ rangeinfo.indexes[i] = -1;
+ rangeinfo.nbounds = num_distinct_bounds;
+
+ result->boundinfo->bounds.ranges = rangeinfo;
+ break;
+ }
+ }
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of the
+ * list values or the range bounds (ie, sorted).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+ pfree(mapping);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2)
+{
+ if (b1->strategy != b2->strategy)
+ return false;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key,
+ &b1->bounds.lists, &b2->bounds.lists))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key,
+ &b1->bounds.ranges, &b2->bounds.ranges))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent. Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+ bool overlap = false;
+
+ parent = heap_open(parentId, NoLock); /* already locked */
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ if (pdesc->nparts > 0)
+ {
+ ListCell *cell;
+ bool new_has_null = false;
+ PartitionListInfo listinfo;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+ Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+ /* If the new partition's list contains null, set a flag. */
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ {
+ new_has_null = true;
+ break;
+ }
+ }
+
+ /*
+ * Now check against listinfo whether the new partition's
+ * values are not already taken by existing partitions.
+ */
+ if (new_has_null && listinfo.has_null)
+ {
+ with = listinfo.null_index;
+ overlap = true;
+ }
+ else
+ {
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+ int found = -1;
+
+ /* bsearch a new list's value in listinfo->values */
+ if (!val->constisnull)
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ val->constvalue);
+ if (found >= 0)
+ {
+ overlap = true;
+ with = listinfo.indexes[found];
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeBound *lower,
+ *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, -1,
+ spec->lowerdatums, spec->lowerinc,
+ true);
+ upper = make_one_range_bound(key, -1,
+ spec->upperdatums, spec->upperinc,
+ false);
+
+ /*
+ * First check if the resulting range would be empty with
+ * specified bounds
+ */
+ if (partition_rbound_cmp(key, lower, upper) > 0 ||
+ (partition_rbound_cmp(key, lower, upper) == 0
+ && (!spec->lowerinc || !spec->upperinc)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, spec->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ PartitionRangeInfo rangeinfo;
+ int offset1, offset2;
+ bool equal = false;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ /*
+ * Find the greatest index of a range bound that is less
+ * than the new lower bound.
+ */
+ offset1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, lower,
+ partition_rbound_cmp, false, &equal);
+
+ /*
+ * If equal has been set to true, that means the new bound is
+ * found to be equal to some existing bound. offset1 == -1
+ * means that all existing range bounds are greater than the
+ * new bound. If offset >= 0, and there exists a gap between
+ * the corresponding bound and the next, new partition can
+ * still manage to fit within the same. To confirm whether it
+ * is so, check the new upper bound.
+ */
+ if (!equal &&
+ (offset1 < 0 || rangeinfo.indexes[offset1+1] < 0))
+ {
+ offset2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, upper,
+ partition_rbound_cmp, false,
+ &equal);
+
+ if (equal || offset1 != offset2)
+ {
+ overlap = true;
+ with = rangeinfo.indexes[offset2+1];
+ }
+ }
+ else
+ {
+ overlap = true;
+ if (offset1 == -1)
+ {
+ Assert(equal);
+ offset1 = 0;
+ }
+ with = rangeinfo.indexes[offset1+1];
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (overlap)
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, spec->location)));
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition check constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual = NIL;
+ TupleDesc parent_tupdesc = RelationGetDescr(parent);
+ AttrNumber parent_attno;
+ AttrNumber *partition_attnos;
+ bool found_whole_row;
+
+ Assert(key != NULL);
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, spec);
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, spec);
+ break;
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partition_attnos = (AttrNumber *)
+ palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+ for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+ parent_attno++)
+ {
+ Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+ char *attname = NameStr(attribute->attname);
+ AttrNumber partition_attno;
+
+ if (attribute->attisdropped)
+ continue;
+
+ partition_attno = get_attnum(RelationGetRelid(rel), attname);
+ partition_attnos[parent_attno - 1] = partition_attno;
+ }
+
+ my_qual = (List *) map_variable_attnos((Node *) my_qual,
+ 1, 0,
+ partition_attnos,
+ parent_tupdesc->natts,
+ &found_whole_row);
+ /* there can never be a whole-row reference in there*/
+ Assert(!found_whole_row);
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result;
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If the list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, remove the null Const from the list and create a IS NULL
+ * test and create an OR expression along with ScalarArrayOpExpr. We do
+ * it this way because the null-valued expression does not have the
+ * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(spec->listdatums); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ spec->listdatums = list_delete_cell(spec->listdatums,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = spec->listdatums;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ if (nulltest1)
+ result = list_make2(nulltest1, opexpr);
+ else if (nulltest2)
+ {
+ Expr *or;
+
+ or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+ result = list_make1(or);
+ }
+ else
+ result = list_make1(opexpr);
+
+ return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+
+ /*
+ * Iterate over columns of the key emitting an OpExpr for each, based
+ * on the corresponding elements of the lower and upper datums.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum = lfirst(cell1),
+ *udatum = lfirst(cell2);
+ Node *key_col;
+ Const *lower_val = NULL,
+ *upper_val = NULL;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+ Oid operoid;
+ uint16 strategy;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * If either of lower or upper datum is infinite, stop at this column,
+ * but do emit an OpExpr for the non-infinite datum.
+ */
+ if (!ldatum->infinite)
+ lower_val = (Const *) ldatum->value;
+ if (!udatum->infinite)
+ upper_val = (Const *) udatum->value;
+
+ /*
+ * If lower_val and upper_val are both finite and happen to be equal,
+ * emit only (key_col = lower_val) for this column, because all rows
+ * in this partition could only ever contain this value (ie, lower_val)
+ * in the current partitioning column. We must consider further
+ * columns because the above condition does not fully constrain the
+ * rows of this partition.
+ */
+ if (lower_val && upper_val)
+ {
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ /* Create a test expression to check if lower_val = upper_val */
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go over to consider the next column. */
+ i++;
+ continue;
+ }
+ }
+
+ /*
+ * We can say here that lower_val <> upper_val. Emit expressions
+ * (key_col >/>= lower_val) and (key_col </<= upper_val), provided
+ * lower_val and upper_val are finite, respectively.
+ */
+ if (lower_val)
+ {
+ strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ if (upper_val)
+ {
+ strategy = i < spec->upperinc ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ operoid = get_partition_operator(key, i, strategy, &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ /*
+ * We can stop at this column, because we would not have checked
+ * the next column when routing a given row into this partition.
+ */
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ /*
+ * First check if there exists an operator of the given strategy, with
+ * this column's type as both its lefttype and righttype, in the
+ * partitioning operator family specified for the column.
+ */
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+
+ /*
+ * If one doesn't exist, we must resort to using an operator, in the same
+ * opreator family, with the operator class declared input type. It is
+ * OK to do so, because the column's type is known to be binary-coercible
+ * with the operator class input type (otherwise, the operator class in
+ * question would not have been accepted as the partitioning operator
+ * class). We must however inform the caller to wrap the non-Const
+ * expression with a RelabelType node to denote the implicit coercion. It
+ * ensures that the resulting expression structurally matches similarly
+ * processed expressions within the optimizer.
+ */
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+ else
+ *need_relabel = false;
+
+ if (!OidIsValid(operoid))
+ elog(ERROR, "could not find operator for partitioning");
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Get pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+ ReleaseSysCache(tuple);
+
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Keep the parent locked until commit */
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+ Datum value1 = (*(const PartitionListValue **) a)->value,
+ value2 = (*(const PartitionListValue **) b)->value;
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = partition_list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums,
+ bool inclusive, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+ int i;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->index = index;
+ bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+ bound->inclusive = inclusive;
+ bound->lower = lower;
+
+ i = 0;
+ foreach (cell, datums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+
+ bound->infinite[i] = datum->infinite;
+
+ if (!bound->infinite[i])
+ {
+ Const *val = (Const *) datum->value;
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ bound->datums[i] = val->constvalue;
+ }
+
+ i++;
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->index = src->index;
+ result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+ result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ for (i = 0; i < partnatts; i++)
+ {
+ result->infinite[i] = src->infinite[i];
+ if (!result->infinite[i])
+ result->datums[i] = datumCopy(src->datums[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+ int i;
+
+ if (r1->nbounds != r2->nbounds)
+ return false;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < r1->nbounds; i++)
+ {
+ if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+ return false;
+
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+ }
+
+ /* There is one more index than bounds */
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+
+ return true;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+ PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int32 cmpval;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ /*
+ * First, handle cases involving infinity, which don't require
+ * invoking the comparison proc.
+ */
+ if (b1->infinite[i] && b2->infinite[i])
+ {
+ /*
+ * 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[i])
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite[i])
+ return b2->lower ? 1 : -1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /*
+ * 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 (cmpval == 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 cmpval;
+}
+
+/*
+ * Are two (consecutive) range bounds equal without distinguishing lower
+ * and upper?
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ int32 cmpval = 0;
+
+ /*
+ * If either of them has infinite element, we can't equate them.
+ * Even if both were infinite, they'd have opposite signs (it always
+ * holds that b1 and b2 are lower and upper bound or vice versa).
+ */
+ if (b1->infinite[i] || b2->infinite[i])
+ return false;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ return false;
+ }
+
+ /*
+ * All datums are found to be equal, now only if both are inclusive
+ * or exclusive then they are considered equal.
+ */
+ if (b1->inclusive == b2->inclusive)
+ return true;
+
+ return false;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+ PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple. The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal)
+{
+ int lo, hi, mid;
+
+ lo = -1;
+ hi = n - 1;
+ while (lo < hi)
+ {
+ PartitionRangeBound *cur;
+ int32 cmpval;
+
+ mid = (lo + hi + 1) / 2;
+ cur = bounds[mid];
+
+ cmpval = cmp(key, cur, probe);
+ if (is_equal)
+ *is_equal = (cmpval == 0);
+
+ if (cmpval < 0 || (equal_allowed && cmpval == 0))
+ lo = mid;
+ else
+ hi = mid - 1;
+ }
+
+ return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
* Create the relation. (This will error out if there's an existing view,
* so we don't need more code to complain if "replace" is false.)
*/
- intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+ intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
/*
* If necessary, create a TOAST table for the target table. Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists;
- address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+ address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
seqoid = address.objectId;
Assert(seqoid != InvalidOid);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 82588a8..de19e9c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -163,6 +164,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +282,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -346,7 +350,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse);
static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +450,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -466,7 +477,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
*/
ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress)
+ ObjectAddress *typaddress, const char *queryString)
{
char relname[NAMEDATALEN];
Oid namespaceId;
@@ -597,7 +608,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
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
@@ -619,6 +631,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
+ if (stmt->partbound)
+ {
+ /* If the parent has OIDs, partitions must have them too. */
+ if (parentOidCount > 0 && !localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table without OIDs as partition of table with OIDs")));
+ /* If the parent doesn't, partitions must not have them. */
+ if (parentOidCount == 0 && localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+ }
+
/*
* Find columns with default values and prepare for insertion of the
* defaults. Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +743,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition bound, if any. */
+ if (stmt->partbound)
+ {
+ Node *bound;
+ ParseState *pstate;
+ Oid parentId = linitial_oid(inheritOids);
+ Relation parentRel;
+
+ /* Already have strong enough lock on the parent */
+ parentRel = heap_open(parentId, NoLock);
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partitioning key of parentRel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* Tranform the bound values */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+ bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+ heap_close(parentRel, NoLock);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(rel, bound);
+
+ /*
+ * The code that follows may also update the pg_class tuple to update
+ * relnumchecks, so bump up the command counter to avoid the "already
+ * updated by self" error.
+ */
+ CommandCounterIncrement();
+ }
+
/*
* Process the partitioning specification (if any) and store the
* partitioning key information into the catalog.
@@ -1096,6 +1167,7 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
+ /* Force inheritance recursion, if partitioned table. */
if (recurse)
{
ListCell *child;
@@ -1117,6 +1189,10 @@ ExecuteTruncate(TruncateStmt *stmt)
relids = lappend_oid(relids, childrelid);
}
}
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("must truncate child tables too")));
}
/*
@@ -1474,7 +1550,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;
@@ -1484,6 +1561,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
+ List *saved_schema;
/*
* Check for and reject tables with too many columns. We perform this
@@ -1508,53 +1586,59 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* Although we might consider merging such entries in the same way that we
* handle name conflicts for inherited attributes, it seems to make more
* sense to assume such conflicts are errors.
+ *
+ * In case of partitions, schema contains only the WITH OPTION entries
+ * at this point, so the following checks are meaningless yet.
*/
- foreach(entry, schema)
+ if (!is_partition)
{
- ColumnDef *coldef = lfirst(entry);
- ListCell *rest = lnext(entry);
- ListCell *prev = entry;
-
- if (coldef->typeName == NULL)
+ foreach(entry, schema)
+ {
+ ColumnDef *coldef = lfirst(entry);
+ ListCell *rest = lnext(entry);
+ ListCell *prev = entry;
/*
* 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.
*/
- ereport(ERROR,
+ if (coldef->typeName == NULL)
+ ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
coldef->colname)));
- while (rest != NULL)
- {
- ColumnDef *restdef = lfirst(rest);
- ListCell *next = lnext(rest); /* need to save it in case we
+ while (rest != NULL)
+ {
+ ColumnDef *restdef = lfirst(rest);
+ ListCell *next = lnext(rest); /* need to save it in case we
* delete it */
- if (strcmp(coldef->colname, restdef->colname) == 0)
- {
- if (coldef->is_from_type)
+ if (strcmp(coldef->colname, restdef->colname) == 0)
{
- /*
- * merge the column options into the column from the type
- */
- 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_from_type = false;
- list_delete_cell(schema, rest, prev);
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_COLUMN),
+ if (coldef->is_from_type)
+ {
+ /*
+ * merge the column options into the column from the
+ * type
+ */
+ 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_from_type = false;
+ list_delete_cell(schema, rest, prev);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" specified more than once",
coldef->colname)));
+ }
+ prev = rest;
+ rest = next;
}
- prev = rest;
- rest = next;
}
}
@@ -1582,18 +1666,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1706,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation \"%s\"",
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation \"%s\""
+ : "cannot create as partition of temporary relation \"%s\"",
parent->relname)));
/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1716,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
!relation->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation of another session")));
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation of another session"
+ : "cannot create as partition of temporary relation of another session")));
/*
* We should have an UNDER permission flag for this, but for now,
@@ -1855,6 +1962,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
heap_close(relation, NoLock);
}
+ /* In case of a partition, there are no new column definitions */
+ if (is_partition)
+ {
+ saved_schema = schema;
+ schema = NIL;
+ }
+
/*
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
@@ -1886,6 +2000,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Oid defcollid,
newcollid;
+ Assert(!is_partition);
+
/*
* Yes, try to merge the two column definitions. They must
* have the same type, typmod, and collation.
@@ -1934,7 +2050,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
+ /* Mark the column as locally defined (unless partition) */
def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
@@ -1968,6 +2084,52 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
/*
+ * Now that we have a column definition list for a partition, we can check
+ * whether the referenced column actually exists.
+ */
+ if (is_partition)
+ schema = list_concat(schema, saved_schema);
+
+ foreach(entry, schema)
+ {
+ ColumnDef *coldef = lfirst(entry);
+ ListCell *rest = lnext(entry);
+ ListCell *prev = entry;
+
+ /*
+ * 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.
+ */
+ if (coldef->typeName == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist",
+ coldef->colname)));
+ while (rest != NULL)
+ {
+ ColumnDef *restdef = lfirst(rest);
+ ListCell *next = lnext(rest); /* need to save it in case we
+ * delete it */
+
+ if (strcmp(coldef->colname, restdef->colname) == 0)
+ {
+ /*
+ * 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;
+ list_delete_cell(schema, rest, prev);
+ }
+ prev = rest;
+ rest = next;
+ }
+ }
+
+ /*
* If we found any conflicting parent default values, check to make sure
* they were overridden by the child.
*/
@@ -2455,7 +2617,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -3129,6 +3291,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);
@@ -3240,12 +3407,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepDropNotNull(rel, recurse);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepSetNotNull(rel, recurse);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3615,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);
@@ -3516,7 +3691,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, the
+ * following check is unnecessary, because we did not modify anything
+ * about it that will change its toasting requirement.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3765,6 +3947,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);
@@ -3950,7 +4138,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4030,6 +4218,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4094,6 +4283,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4481,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("partition constraint is violated by some row")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4683,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4802,6 +5006,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5248,6 +5457,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be dropped from child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+}
static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
@@ -5323,6 +5545,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
+ /* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
/*
* Okay, actually perform the catalog change ... if needed
*/
@@ -5355,6 +5594,20 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
* Return the address of the modified column. If the column was already NOT
* NULL, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be added to the child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be added to child tables too")));
+}
+
static ObjectAddress
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6167,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
Relation attr_rel;
ListCell *child;
+ /*
+ * In case of a partitioned table, the column must be dropped from the
+ * partitions as well.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column must be dropped from child tables too")));
+
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(child, children)
{
@@ -7916,6 +8178,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
}
/*
+ * In case of a partitioned table, the constraint must be dropped from
+ * the partitions too. There is no such thing as NO INHERIT constraints
+ * in case of partitioned tables.
+ */
+ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+
+ /*
* 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.
@@ -10217,6 +10489,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10506,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;
@@ -10279,37 +10551,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
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);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10590,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);
@@ -10358,16 +10667,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;
}
/*
@@ -10418,7 +10719,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
@@ -10436,12 +10737,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10794,14 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
* later on, this change will just roll back.)
*/
childatt->attinhcount++;
+
+ /*
+ * In case of partitions, we must enforce that value of attislocal
+ * is same in all partitions. (Note: there are only inherited
+ * attributes in partitions)
+ */
+ if (is_attach_partition)
+ childatt->attislocal = false;
simple_heap_update(attrrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrrel, tuple);
heap_freetuple(tuple);
@@ -10511,7 +10824,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.
@@ -10530,10 +10843,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10610,6 +10927,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (is_attach_partition)
+ {
+ Assert(child_con->coninhcount == 1);
+ child_con->conislocal = false;
+ }
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10634,6 +10963,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10647,13 +11016,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];
@@ -10662,19 +11029,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
-
- /*
- * 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);
+ bool is_detach_partition = false;
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11042,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);
@@ -10705,11 +11063,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
@@ -10718,7 +11085,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)))
@@ -10780,7 +11147,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);
@@ -10811,7 +11178,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)
@@ -10823,30 +11190,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;
}
/*
@@ -12531,3 +12888,285 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * 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,
+ inheritsRel;
+ List *childrels;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ /* A partition can only have one parent */
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /*
+ * attachRel should not already be part of inheritance; either as a child
+ * table...
+ */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId, true,
+ NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach inheritance child as partition")));
+ systable_endscan(scan);
+
+ /* ...or be a RELKIND_RELATION parent table */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true, NULL,
+ 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)) &&
+ attachRel->rd_rel->relkind == RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach regular inheritance parent as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /*
+ * Prevent circularity by seeing if rel is a partition of attachRel.
+ * (In particular, this disallows making a rel a partition of itself.)
+ */
+ childrels = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessShareLock, NULL);
+ if (list_member_oid(childrels, RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("circular inheritance not allowed"),
+ errdetail("\"%s\" is already a child of \"%s\".",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(attachRel))));
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("New partition should contain only the columns present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(attachRel, cmd->bound);
+
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ List *leaf_parts;
+ List *parent_quals;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ leaf_parts = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessExclusiveLock, NULL);
+ else
+ leaf_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ parent_quals = RelationGetPartitionQual(rel, true);
+ foreach(lc, leaf_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid leaf_relid = lfirst_oid(lc);
+ Relation leaf_rel;
+ List *my_quals;
+
+ /* Lock already taken */
+ if (leaf_relid != RelationGetRelid(attachRel))
+ leaf_rel = heap_open(leaf_relid, NoLock);
+ else
+ leaf_rel = attachRel;
+
+ /*
+ * Skip if it's a partitioned table. Only RELKIND_RELATION
+ * relations need to be scanned.
+ */
+ if (leaf_rel != attachRel &&
+ leaf_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(leaf_rel, NoLock);
+ continue;
+ }
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, leaf_rel);
+
+ /*
+ * We've got to check only the bound condition specified in this
+ * command and the parent's bound condition if it happens to be a
+ * partition.
+ */
+ my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound);
+ tab->partition_quals = list_concat(parent_quals, my_quals);
+
+ /* keep our lock until commit */
+ if (leaf_rel != attachRel)
+ heap_close(leaf_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that this partition is now included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
/*
* Finally create the relation. This also creates the type.
*/
- DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+ DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+ NULL);
return address;
}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
- address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+ address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+ NULL);
Assert(address.objectId != InvalidOid);
return address;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..c0d48d0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4215,6 +4216,46 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+ PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(listdatums);
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lowerdatums);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upperdatums);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+ PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+ COPY_SCALAR_FIELD(infinite);
+ COPY_NODE_FIELD(value);
+ 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
* ****************************************************************
@@ -5138,6 +5179,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundSpec:
+ retval = _copyPartitionBoundSpec(from);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _copyPartitionRangeDatum(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 7d0391d..c2cb140 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,40 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(listdatums);
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lowerdatums);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upperdatums);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+ COMPARE_SCALAR_FIELD(infinite);
+ COMPARE_NODE_FIELD(value);
+ 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
*/
@@ -3430,6 +3465,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundSpec:
+ retval = _equalPartitionBoundSpec(a, b);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _equalPartitionRangeDatum(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
+ case T_PartitionBoundSpec:
+ loc = ((const PartitionBoundSpec *) expr)->location;
+ break;
+ case T_PartitionRangeDatum:
+ loc = ((const PartitionRangeDatum *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..d5eb0cc 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,28 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUND");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(listdatums);
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lowerdatums);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+ WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+ WRITE_BOOL_FIELD(infinite);
+ WRITE_NODE_FIELD(value);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3916,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundSpec:
+ _outPartitionBoundSpec(str, obj);
+ break;
+ case T_PartitionRangeDatum:
+ _outPartitionRangeDatum(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..655aa1c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,38 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+ READ_LOCALS(PartitionBoundSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_NODE_FIELD(listdatums);
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lowerdatums);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upperdatums);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+ READ_LOCALS(PartitionRangeDatum);
+
+ READ_BOOL_FIELD(infinite);
+ READ_NODE_FIELD(value);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2529,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONBOUND", 14))
+ return_value = _readPartitionBoundSpec();
+ else if (MATCH("PARTRANGEDATUM", 14))
+ return_value = _readPartitionRangeDatum();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 76c0793..c9f1d79 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionSpec *partspec;
+ PartitionRangeDatum *partrange_datum;
}
%type <node> stmt schema_stmt
@@ -551,6 +552,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partbound_datum
+%type <list> partbound_datum_list
+%type <boolean> lb_inc ub_inc
+%type <partrange_datum> PartitionRangeDatum
+%type <list> range_datum_list
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -576,7 +586,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
@@ -592,7 +602,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
@@ -606,7 +617,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
@@ -2378,6 +2389,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:
@@ -2473,6 +2516,88 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partbound_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatums = $5;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START '(' range_datum_list ')' lb_inc
+ END_P '(' range_datum_list ')' ub_inc
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ n->lowerinc = $7;
+ n->lowerdatums = $5;
+ n->upperinc = $12;
+ n->upperdatums = $10;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+partbound_datum:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partbound_datum_list:
+ partbound_datum { $$ = list_make1($1); }
+ | partbound_datum_list ',' partbound_datum
+ { $$ = lappend($1, $3); }
+ ;
+
+range_datum_list:
+ PartitionRangeDatum { $$ = list_make1($1); }
+ | range_datum_list ',' PartitionRangeDatum
+ { $$ = lappend($1, $3); }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+PartitionRangeDatum:
+ UNBOUNDED
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = true;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | partbound_datum
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = false;
+ n->value = $1;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
/*****************************************************************************
*
@@ -2890,6 +3015,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2935,6 +3098,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2957,6 +3125,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2968,6 +3147,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4739,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;
+ }
;
/*****************************************************************************
@@ -13804,6 +14030,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13850,6 +14077,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13892,6 +14120,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..6a4409a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ 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 transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
/*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ 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;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3045,234 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partitioning key of rel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* tranform the values */
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+ cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification as per the partitioning key
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+ *result_spec;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+
+ result_spec = copyObject(spec);
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListCell *cell;
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ result_spec->listdatums = NIL;
+ foreach(cell, spec->listdatums)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+ ListCell *cell2;
+ bool duplicate;
+
+ value = (Node *) make_const(pstate, &con->val, con->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(pstate,
+ exprLocation((Node *) con))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ /* Don't add to the result if the value is a duplicate */
+ duplicate = false;
+ foreach(cell2, result_spec->listdatums)
+ {
+ Const *value2 = (Const *) lfirst(cell2);
+
+ if (equal(value, value2))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+
+ result_spec->listdatums = lappend(result_spec->listdatums,
+ value);
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ ListCell *cell1,
+ *cell2;
+ int i,
+ j;
+ char *colname;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+ if (list_length(spec->lowerdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START must specify exactly one value per partitioning column")));
+ if (list_length(spec->upperdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END must specify exactly one value per partitioning column")));
+
+ i = j = 0;
+ result_spec->lowerdatums = result_spec->upperdatums = NIL;
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum,
+ *rdatum;
+ Node *value;
+ A_Const *lcon = NULL,
+ *rcon = NULL;
+
+ ldatum = (PartitionRangeDatum *) lfirst(cell1);
+ rdatum = (PartitionRangeDatum *) lfirst(cell2);
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ if (!ldatum->infinite)
+ lcon = (A_Const *) ldatum->value;
+ if (!rdatum->infinite)
+ rcon = (A_Const *) rdatum->value;
+
+ if (lcon)
+ {
+ value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ ldatum->value = value;
+ }
+
+ if (rcon)
+ {
+ value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ rdatum->value = value;
+ }
+
+ result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+ copyObject(ldatum));
+ result_spec->upperdatums = lappend(result_spec->upperdatums,
+ copyObject(rdatum));
+
+ ++i;
+ }
+
+ break;
+ }
+ }
+
+ return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_RELATION,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_FOREIGN_TABLE,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
CreateForeignTable((CreateForeignTableStmt *) stmt,
address.objectId);
EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index eaf809a..d3979b4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? If the partitioning structure did not change, that is,
+ * no partitions were added or removed to the relation, the oids array
+ * should still match element-by-element.
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The logic to iterate over
+ * the collections is local to partition.c.
+ */
+ if (pdesc1->boundinfo != NULL)
+ {
+ if (pdesc2->boundinfo == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key,
+ pdesc1->boundinfo, pdesc2->boundinfo))
+ return false;
+ }
+ else if (pdesc2->boundinfo != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2608,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 */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
RelationBuildPartitionKey(relation);
Assert(relation->rd_partkey != NULL);
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
restart = true;
}
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
Oid *partopclass,
Oid *partcollation);
extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
#endif /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..e1c01d2
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds. It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection boundinfo; /* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress);
+ ObjectAddress *typaddress, const char *queryString);
extern void RemoveRelations(DropStmt *drop);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundSpec,
+ T_PartitionRangeDatum,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a04dbeb..d648774 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+ NodeTag type;
+
+ char strategy;
+
+ /* List partition values */
+ List *listdatums;
+
+ /* Range partition lower and upper bound spec*/
+ bool lowerinc;
+ List *lowerdatums;
+ bool upperinc;
+ List *upperdatums;
+
+ int location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+ NodeTag type;
+
+ bool infinite;
+ Node *value;
+
+ int location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1570,7 +1615,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
@@ -1796,7 +1843,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..7b290c0 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_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+ Node *bound);
#endif /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index e751491..c3d8c86 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partitioning 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 */
@@ -601,6 +604,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index c39fac5..f648fce 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,240 @@ ERROR: cannot inherit from partitioned table "partitioned"
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, foo;
+-- ATTACH PARTITION
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES START (1) ...
+ ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR: cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR: cannot attach regular inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table child
+-- check that 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 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 that the table being attached has only columns present 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: New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child 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: child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing constraint "check_a"
+-- check that the constraint 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: child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+ERROR: partition constraint is violated by some row
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+ERROR: partition constraint is violated by some row
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- check that 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
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ERROR: circular inheritance not allowed
+DETAIL: "part_3" is already a child of "list_parted".
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+ERROR: circular inheritance not allowed
+DETAIL: "list_parted" is already a child of "list_parted".
+-- DETACH PARTITION
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check that the partition being detached is actually 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, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal
+-------------+------------
+ 0 | t
+ 0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal
+-------------+------------
+ 0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR: column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR: column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+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 inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR: constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR: constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR: constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR: constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partitioning key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partitioning key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index bec5655..fbdfaea 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,193 @@ Partitioning key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
Partitioning key: LIST ((a + 1))
DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES START (1) ...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR: specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+ERROR: START must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+ERROR: END must specify exactly one value per partitioning column
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES START (1) END (10 )WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES START (1) END (10 ) WITHOUT OIDS;
+ERROR: cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+ERROR: cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+ERROR: partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+ERROR: partition "fail_part" would overlap partition "part2"
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+ERROR: partition "fail_part" would overlap partition "part2"
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+ERROR: partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partitioning key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 16 other objects
+DETAIL: drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part3
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..58c9d98 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,201 @@ ALTER TABLE foo INHERIT partitioned;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, foo;
+
+-- ATTACH PARTITION
+
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that 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 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 that the table being attached has only columns present 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 that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+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 that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+
+-- specifying NO VALIDATE skips the scan
+ALTER TABLE fail_part RENAME TO part_2;
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE;
+DELETE FROM part_2 WHERE a NOT IN (2);
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f100498..1d03720 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
\d partitioned2
DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES START (1) END (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES START (1) END (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES START (1) END (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (1) END (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES START (unbounded) END (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (unbounded) END (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE;
+CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE;
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (10) END (20);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES START (9) END (20);
+CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES START (11) END (20);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (0, unbounded) END (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES START (1, 1) END (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, 10) END (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES START (1, unbounded) END (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+
+-- partitions 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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-11.patch (22.3K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-11.patch)
download | inline diff:
From b8662af27ec907b2929cf6a2d935b8ead977a1c9 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 | 86 ++++++++++++++++++++
src/bin/pg_dump/common.c | 86 ++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 118 ++++++++++++++++++++++++++--
src/bin/pg_dump/pg_dump.h | 12 +++
src/bin/psql/describe.c | 85 +++++++++++++++++---
src/test/regress/expected/create_table.out | 40 ++++++++++
src/test/regress/sql/create_table.sql | 12 +++
7 files changed, 419 insertions(+), 20 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 319531c..5662bad 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,92 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundSpec:
+ {
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ switch (spec->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->listdatums != NIL);
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->lowerdatums != NIL &&
+ spec->upperdatums != NIL &&
+ list_length(spec->lowerdatums) ==
+ list_length(spec->upperdatums));
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " START");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->lowerdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ if (!spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+
+ appendStringInfoString(buf, " END");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->upperdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ if (spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ break;
+
+ default:
+ elog(ERROR, "unrecognized partition strategy: %d",
+ (int) spec->strategy);
+ break;
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index c12c490..b1c4457 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);
@@ -323,6 +338,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
*
@@ -924,6 +976,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 c393264..834e50d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5595,9 +5595,16 @@ getInherits(Archive *fout, int *numInherits)
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
- /* find all the inheritance information */
-
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ /*
+ * Find all the inheritance information, excluding implicit inheritance
+ * via partitioning. We handle that case using getPartitions(), because
+ * we want more information about partitions than just the parent-child
+ * relationship.
+ */
+ 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);
@@ -5624,6 +5631,70 @@ 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;
+
+ /* Before version 10, there are no partitions */
+ if (fout->remoteVersion < 100000)
+ {
+ *numPartitions = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find the inheritance and boundary information about partitions */
+
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -14155,6 +14226,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 */
@@ -14183,8 +14265,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;
@@ -14213,7 +14298,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");
}
@@ -14271,15 +14357,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 (");
@@ -14449,6 +14542,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 a75e79c..fc0a345 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;
@@ -625,6 +636,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 6d44378..ca8ff5f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partitioning key information */
@@ -2535,8 +2563,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)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fbdfaea..829d05e 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,46 @@ ERROR: column "c" named in partitioning key does not exist
CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
-- create a level-2 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 | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+ "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+ Table "public.part_c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partitioning key: RANGE (b)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partitioning key: LIST (a)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
-- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 1d03720..4c0deb6 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
-- create a level-2 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
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Teach-a-few-places-to-use-partition-check-quals-11.patch (30.1K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/6-0005-Teach-a-few-places-to-use-partition-check-quals-11.patch)
download | inline diff:
From d5425a707788da02a3c9984c30984542da49d515 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 quals.
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 | 75 +++++++++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/optimizer/util/plancat.c | 20 +++
src/include/nodes/execnodes.h | 4 +
src/test/regress/expected/inherit.out | 255 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 ++++++++++
src/test/regress/expected/update.out | 27 ++++
src/test/regress/sql/inherit.sql | 47 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 581 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0ef590a..77d4dcb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2503,7 +2503,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 9773272..ea3f59a 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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,28 @@ 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 for relation \"%s\" violates partition constraint",
+ 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 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,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)
@@ -922,7 +922,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 ad07baa..a2cbf14 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"
@@ -1139,6 +1140,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.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition predicates, if any */
+ pcqual = RelationGetPartitionQual(relation, true);
+ 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 f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index b331828..4a3b136 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,258 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..ebc0ba5 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 for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+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 for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+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 for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL: Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR: new row for relation "part_ee_ff_1_10" violates partition constraint
+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 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..9813243 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 for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..72c19e8 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,50 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values start (1) end (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values start (10) end (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values start (21) end (30) inclusive partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values start (40) end (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
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
[text/x-diff] 0006-Introduce-a-PartitionTreeNode-data-structure-11.patch (8.0K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/7-0006-Introduce-a-PartitionTreeNode-data-structure-11.patch)
download | inline diff:
From cde4510555d55795358f9089e803226ca0d61d34 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 | 208 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 213 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3ae8cf6..69c3e52 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
int index;
} PartitionListValue;
+/*
+ * 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_parts 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;
+
static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
static List *generate_partition_qual(Relation rel, bool recurse);
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -887,6 +945,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1325,6 +1430,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 e1c01d2..ab6d3a8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -51,4 +52,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
extern Oid get_partition_parent(Oid relid);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0007-Tuple-routing-for-partitioned-tables-11.patch (43.7K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/8-0007-Tuple-routing-for-partitioned-tables-11.patch)
download | inline diff:
From 5a7933f07ce8a9a4eea2a1b4ca603926c8e09ad2 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 | 327 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 +++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 +++++-
src/backend/executor/nodeModifyTable.c | 142 +++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 77 +++++++
src/backend/optimizer/util/plancat.c | 13 ++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 59 ++++++-
src/test/regress/sql/insert.sql | 28 +++
18 files changed, 926 insertions(+), 9 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 69c3e52..9cba603 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -195,6 +207,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
static bool equal_range_info(PartitionKey key,
PartitionRangeInfo *r1, PartitionRangeInfo *r2);
static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg);
static bool partition_rbound_eq(PartitionKey key,
PartitionRangeBound *b1, PartitionRangeBound *b2);
typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1449,7 +1463,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;
@@ -1533,6 +1547,284 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ PartitionListInfo listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+
+ if (isnull && listinfo.has_null)
+ return listinfo.null_index;
+ else if (!isnull)
+ {
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ value);
+ if (found >= 0)
+ return listinfo.indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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)
+{
+ int offset;
+ PartitionRangeInfo rangeinfo;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ offset = partition_rbound_bsearch(key,
+ rangeinfo.bounds, rangeinfo.nbounds,
+ tuple, partition_rbound_datum_cmp,
+ true, NULL);
+
+ /*
+ * Offset returned is such that the bound at offset is found to be
+ * less or equal with the tuple. That is, the tuple belongs to the
+ * partition with the rangeinfo.bounds[offset] as the lower bound and
+ * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+ * is indeed marked !lower (that is, it's an upper bound). If it turns
+ * out that it is a lower bound then the corresponding index will be -1,
+ * which means no valid partition exists.
+ */
+ return rangeinfo.indexes[offset+1];
+}
+
/* List partition related support functions */
/*
@@ -1798,6 +2090,39 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
}
/*
+ * Return whether the passed in range bound <=, =, >= tuple specified in arg
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg)
+{
+ Datum *datums1 = bound->datums,
+ *datums2 = (Datum *) arg;
+ int i;
+ int32 cmpval;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ if (bound->infinite[i])
+ return bound->lower ? -1 : 1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ if (cmpval == 0 && !bound->inclusive)
+ return bound->lower ? 1 : -1;
+
+ return cmpval;
+}
+
+/*
* Are two (consecutive) range bounds equal without distinguishing lower
* and upper?
*/
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 77d4dcb..91e4d12 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.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1705,6 +1801,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);
}
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2275,7 +2389,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2383,6 +2498,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2410,6 +2526,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;
@@ -2431,10 +2548,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 */
@@ -2486,6 +2639,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 */
@@ -2506,7 +2684,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)
@@ -2536,7 +2723,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),
@@ -2556,6 +2744,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2569,7 +2763,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 de19e9c..88b9d1f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1292,6 +1292,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 ea3f59a..62bf8b7 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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);
@@ -2996,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 a612b08..d0a5306 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -272,6 +273,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;
/*
@@ -511,6 +537,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
@@ -1565,6 +1597,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)));
@@ -1655,6 +1688,98 @@ 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_TABLE)
+ {
+ 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)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ 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.
*/
@@ -1972,6 +2097,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c0d48d0..ca48547 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d5eb0cc..7a219e5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 655aa1c..d047160 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 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_inherits_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ /*
+ * We are only interested in foreign tables. Note that this will
+ * also eliminate any partitioned tables since foreign tables can
+ * only ever be leaf partitions.
+ */
+ if (!oid_is_foreign_table(myoid))
+ continue;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 ab6d3a8..09727cf 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/rel.h"
@@ -56,4 +58,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 136276b..c62946f 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 ff8b66b..ce01008 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"
@@ -1147,6 +1148,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 ebc0ba5..3d97742 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 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
[text/x-diff] 0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-11.patch (24.7K, ../../795e4902-b70c-a18c-7e88-c40effee0f06@lab.ntt.co.jp/9-0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-11.patch)
download | inline diff:
From 2ecc0409bb3f3067f7812a7bf8440759e3e9a9ad 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 157512c..fe76ab0 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,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</>.
@@ -2793,12 +2793,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>
@@ -2842,59 +2845,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 >= 100 AND outletID < 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>
@@ -2911,8 +2877,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>
@@ -2940,7 +2908,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
@@ -2971,12 +2939,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,
@@ -2986,36 +2954,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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3024,110 +2965,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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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">
@@ -3145,22 +2995,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
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
were created above:
<programlisting>
-CREATE TABLE measurement_y2008m02 (
- CHECK ( logdate >= DATE '2008-02-01' AND logdate < 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
@@ -3186,13 +3029,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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
<programlisting>
SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= DATE '2017-01-01';
</programlisting>
Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate >= 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>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
</programlisting>
Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3397,9 +3173,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>
@@ -3423,18 +3199,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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-09 17:00 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 3 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-09 17:00 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
In this latest patch set:
src/backend/parser/parse_utilcmd.c:3194: indent with spaces.
+ *rdatum;
With all patches applied, "make check" fails with a bunch of diffs
that look like this:
Check constraints:
- "pt1chk2" CHECK (c2 <> ''::text)
"pt1chk3" CHECK (c2 <> ''::text)
On Wed, Nov 9, 2016 at 6:14 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Actually the sentence there is: The parenthesized list of columns or
> expressions forms the the <firstterm>partitioning key</firstterm> for the
> table
OK.
>> + /*
>> + * Strip any top-level COLLATE clause. This ensures that we treat
>> + * "x COLLATE y" and "(x COLLATE y)" alike.
>> + */
>>
>> But you don't, right? Unless I am confused, which is possible, the
>> latter COLLATE will be ignored, while the former one will set the
>> collation to be used in the context of partitioning comparisons.
>
> The code immediately following the comment does in fact strip the
> top-level COLLATE clause.
>
> while (IsA(expr, CollateExpr))
> expr = (Node *) ((CollateExpr *) expr)->arg;
>
> So that the following two specifications are equivalent which is the intent:
>
> create table p (a text) partition by range (a collate "en_US");
> vs.
> create table p (a text) partition by range ((a collate "en_US"));
I see. You're right.
>> Re-reviewing 0002:
>>
>> + if (fout->remoteVersion >= 100000)
>> + {
>> + PQExpBuffer acl_subquery = createPQExpBuffer();
>> + PQExpBuffer racl_subquery = createPQExpBuffer();
>> + PQExpBuffer initacl_subquery = createPQExpBuffer();
>> + PQExpBuffer initracl_subquery = createPQExpBuffer();
>> +
>> + PQExpBuffer attacl_subquery = createPQExpBuffer();
>> + PQExpBuffer attracl_subquery = createPQExpBuffer();
>> + PQExpBuffer attinitacl_subquery = createPQExpBuffer();
>> + PQExpBuffer attinitracl_subquery = createPQExpBuffer();
>>
>> It seems unnecessary to repeat all of this. The only differences
>> between the 10000 version and the 9600 version are:
>>
>> 60,61c60
>> < "AS changed_acl, "
>> < "CASE WHEN c.relkind = 'P' THEN
>> pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
>> ---
>>> "AS changed_acl "
>> 73c72
>> < "WHERE c.relkind in ('%c', '%c', '%c', '%c',
>> '%c', '%c', '%c') "
>> ---
>>> "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
>> 87,88c86
>> < RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
>> < RELKIND_PARTITIONED_TABLE);
>> ---
>>> RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
>>
>> But none of that is really a problem. Sure, the 'P' case will never
>> arise in 9.6, but so what? I'd really like to not keep duplicating
>> these increasingly-complex hunks of code if we can find some way to
>> avoid that.
>
> We cannot reference pg_catalog.pg_get_partkeydef() in the SQL query that
> getTables() sends to pre-10 servers, right? But I suppose we need not
> call it in that particular SQL query in the first place.
Oh, yeah, that's a problem; the query will error out against older
servers. You could do something like:
char *partkeydef;
if (version <= 90600)
partkeydef = "NULL";
else
partkeydef = "CASE WHEN c.relkind = 'P' THEN
pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END";
...and the use %s to interpolate that into the query string.
> How about we do it in the following manner in getSchemaData():
>
> if (g_verbose)
> write_msg(NULL, "reading partition key information for interesting
> tables\n");
> getTablePartitionKeyInfo(fout, tblinfo, numTables);
>
> I have implemented the same.
That might be OK too; I'll have to read it through carefully.
>> Re-reviewing 0003:
>>
>> + <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.
>> + </para>
>>
>> This would, presumably, also be true for inheritance. I think we
>> could just leave this out.
>
> Actually, it isn't true for the regular inheritance situation.
>
> create table parent (a int not null);
> create table child () inherits (parent);
> alter table child alter a drop not null; -- this works (bug?)
Hrm, OK. That doesn't satisfy MY idea of the principle of least
astonishment, but hey...
> But in the meantime, we can proceed with enforcing inheritance on NOT NULL
> constraint for *partitions*, because they only ever have one parent and
> hence do not need elaborate coninhcount based scheme, I think. Thoughts?
I agree.
>> + correspond to the partitioning method and partitioning key of the target
>>
>> I think that in most places were are referring to the "partitioning
>> method" (with ing) but the "partition key" (without ing). Let's try to
>> be consistent.
>
> I'm inclined to switch to "partitioning method" and "partitioning key",
> but do you mean just the documentation or throughout? Beside
> documentation, I mean source code comments, error messages, etc. I have
> assumed throughout.
I think "partitioning key" is a bit awkward and actually prefer
"partiton key". But "partition method" sounds funny so I would go
with "partitioning method".
>> + A full table scan is performed on the table being attached to check that
>> + existing rows in the table are within the specified partition bound.
>> + If it is known in advance that no partition bound violating rows are
>> + present in the table, the above scan can be skipped by specifying the
>> + <literal>NO VALIDATE</> option.
>> + Default behavior is to perform the scan, as if the
>> <literal>VALIDATE</literal>
>> + option were specified.
>>
>> I don't think it's OK to allow the partition to be added if it
>> contains rows that might not be valid. We are generally vary wary
>> about adding options that can cause data integrity failures and I
>> think we shouldn't start here, either. On the other hand, it's also
>> not desirable for adding a partition to take O(n) time in all cases.
>> So what would be nice is if the new partition could self-certify that
>> contains no problematic rows by having a constraint that matches the
>> new partitioning constraint. Then we can skip the scan if that
>> constraint is already present.
>
> I agree that NO VALIDATE is undesirable and it would be better if we could
> get rid of the same. I want to clarify one thing though: what does it
> mean for the new partition to have a constraint that *matches* the new
> partitioning constraint? Does it mean the new partition's constraint
> *implies* the partitioning constraint? Or as certified by equal()?
Implies would be better, but equal() might be tolerable.
>> + /*
>> + * Never put a
>> null into the values array, flag
>> + * instead for
>> the code further down below where
>> + * we
>> construct the actual relcache struct.
>> + */
>> +
>> found_null_partition = true;
>> +
>> null_partition_index = i;
>>
>> How about we elog(ERROR, ...) if found_null_partition is already set?
>
> Makes sense. However, let me mention here that duplicates either within
> one partition's list or across the partitions are not possible. That's
> because in case of the former, we de-duplicate before storing the list
> into the catalog and the latter would simply be an overlap error. Could
> this be made an Assert() then?
Well, I think that wouldn't be as good, because for example some
misguided person might do a manual update of the catalog. It seems to
me that if the system catalog contents are questionable, it's better
to error out than to have completely undefined behavior. In other
words, we probably want it checked in non-Assert builds. See similar
cases where we have things like:
elog(ERROR, "conpfeqop is not a 1-D Oid array");
>> + /*
>> + * Is lower_val = upper_val?
>> + */
>> + if (lower_val && upper_val)
>>
>> So, that comment does not actually match that code. That if-test is
>> just checking whether both bounds are finite. What I think you should
>> be explaining here is that if lower_val and upper_val are both
>> non-infinite, and if the happen to be equal, we want to emit an
>> equality test rather than a >= test plus a <= test because $REASON.
>
> OK, I have added some explanatory comments around this code.
So, I was kind of assuming that the answer to "why are we doing this"
was "for efficiency". It's true that val >= const AND const <= val
can be simplified to val = const, but it wouldn't be necessary to do
so for correctness. It just looks nicer and runs faster.
By the way, if you want to keep the inclusive stuff, I think you
probably need to consider this logic in light of the possibility that
one bound might be exclusive. Maybe you're thinking that can't happen
anyway because it would have been rejected earlier, but an elog(ERROR,
...) might still be appropriate just to guard against messed-up
catalogs.
> I forgot this instance of forced-recursion-if-partitioned, fixed. I was
> not quite sure what the error message would say; how about:
>
> ereport(ERROR,
> (errcode(ERRCODE_WRONG_OBJECT_TYPE),
> errmsg("must truncate child tables too")));
Works fror me.
>> What happens if you try to attach a table as a partition of itself or
>> one of its ancestors?
>
> The latter fails with "already a partition" error.
>
> The former case was not being handled at all which has now been fixed.
> ATExecAddInherit() prevents that case as an instance of preventing the
> circularity of inheritance. It says: ERROR: circular inheritance not
> allowed. And then: DETAIL: "rel" is already a child of "rel".
>
> Should ATExecAttachPartition() use the same trick and keep using the same
> message(s)? The above detail message doesn't quite sound appropriate when
> one tries to attach a table as partition of itself.
Whichever way is easier to code seems fine. It's a pretty obscure
case, so I don't think we need to add code just to get a very slightly
better error message.
>> + | FOR VALUES START '(' range_datum_list ')' lb_inc
>> + END_P '('
>> range_datum_list ')' ub_inc
>>
>> Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
>> idiomatic than START and END?
>
> It would actually. Should I go ahead and change to FROM (...) TO (...)?
+1!
> Related to range partitioning, should we finalize on inclusive START/FROM
> and exclusive END/TO preventing explicit specification of the inclusivity?
I would be in favor of committing the initial patch set without that,
and then considering the possibility of adding it later. If we
include it in the initial patch set we are stuck with it.
> Attached updated patches take care of the above comments and few other
> fixes. There are still a few items I have not addressed right away:
>
> - Remove NO VALIDATE clause in ATTACH PARTITION and instead rely on the
> new partition's constraints to skip the validation scan
> - Remove the syntax to specify inclusivity of each of START and END bounds
> of range partitions and instead assume inclusive START and exclusive END
OK - what is the time frame for these changes?
> Also, what used to be "[PATCH 5/9] Refactor optimizer's inheritance set
> expansion code" is no longer included in the set. I think we can go back
> to that topic later as I mentioned last week [2].
Great.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-09 17:10 Robert Haas <robertmhaas@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
2 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-09 17:10 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Nov 9, 2016 at 12:00 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> In this latest patch set:
>
> src/backend/parser/parse_utilcmd.c:3194: indent with spaces.
> + *rdatum;
>
> With all patches applied, "make check" fails with a bunch of diffs
> that look like this:
>
> Check constraints:
> - "pt1chk2" CHECK (c2 <> ''::text)
> "pt1chk3" CHECK (c2 <> ''::text)
And the pg_upgrade test also fails:
Done
+ pg_dumpall -f /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
+ pg_ctl -m fast stop
waiting for server to shut down.... done
server stopped
+ set +x
Files /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql and
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql differ
dumps were not identical
make[2]: *** [check] Error 1
make[1]: *** [check-pg_upgrade-recurse] Error 2
make: *** [check-world-src/bin-recurse] Error 2
[rhaas pgsql]$ diff
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql
/Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
6403d6402
< c text
8736,8737c8735
< CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double
precision)),
< CONSTRAINT bmerged CHECK (((b)::double precision > (1)::double precision))
---
> CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double precision))
For future revisions, please make sure "make check-world" passes before posting.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-10 02:58 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
2 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-10 02:58 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/10 2:00, Robert Haas wrote:
> In this latest patch set:
>
> src/backend/parser/parse_utilcmd.c:3194: indent with spaces.
> + *rdatum;
This one I will fix.
>
> With all patches applied, "make check" fails with a bunch of diffs
> that look like this:
>
> Check constraints:
> - "pt1chk2" CHECK (c2 <> ''::text)
> "pt1chk3" CHECK (c2 <> ''::text)
Hm, I can't seem to reproduce this one. Is it perhaps possible that you
applied the patches on top of some other WIP patches or something?
> And the pg_upgrade test also fails:
>
> Done
> + pg_dumpall -f /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
> + pg_ctl -m fast stop
> waiting for server to shut down.... done
> server stopped
> + set +x
>
> Files /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql and
> /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql differ
> dumps were not identical
> make[2]: *** [check] Error 1
> make[1]: *** [check-pg_upgrade-recurse] Error 2
> make: *** [check-world-src/bin-recurse] Error 2
> [rhaas pgsql]$ diff
> /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump1.sql
> /Users/rhaas/pgsql/src/bin/pg_upgrade/tmp_check/dump2.sql
> 6403d6402
> < c text
> 8736,8737c8735
> < CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double
> precision)),
> < CONSTRAINT bmerged CHECK (((b)::double precision > (1)::double
precision))
> ---
> > CONSTRAINT blocal CHECK (((b)::double precision < (1000)::double
precision))
This one too I can't seem to reproduce.
> For future revisions, please make sure "make check-world" passes before
posting.
OK, I will make sure. FWIW, make check-world passes here after applying
the patches posted yesterday.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-10 12:40 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
2 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-10 12:40 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Thanks again for the prompt review!
On 2016/11/10 2:00, Robert Haas wrote:
> On Wed, Nov 9, 2016 at 6:14 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> But none of that is really a problem. Sure, the 'P' case will never
>>> arise in 9.6, but so what? I'd really like to not keep duplicating
>>> these increasingly-complex hunks of code if we can find some way to
>>> avoid that.
>>
>> We cannot reference pg_catalog.pg_get_partkeydef() in the SQL query that
>> getTables() sends to pre-10 servers, right? But I suppose we need not
>> call it in that particular SQL query in the first place.
>
> Oh, yeah, that's a problem; the query will error out against older
> servers. You could do something like:
>
> char *partkeydef;
> if (version <= 90600)
> partkeydef = "NULL";
> else
> partkeydef = "CASE WHEN c.relkind = 'P' THEN
> pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END";
>
> ...and the use %s to interpolate that into the query string.
Yeah, that's a way.
>>> I think that in most places were are referring to the "partitioning
>>> method" (with ing) but the "partition key" (without ing). Let's try to
>>> be consistent.
>>
>> I'm inclined to switch to "partitioning method" and "partitioning key",
>> but do you mean just the documentation or throughout? Beside
>> documentation, I mean source code comments, error messages, etc. I have
>> assumed throughout.
>
> I think "partitioning key" is a bit awkward and actually prefer
> "partiton key". But "partition method" sounds funny so I would go
> with "partitioning method".
OK, "partition key" and "partitioning method" it is then. Source code
comments, error messages, variables call the latter (partitioning)
"strategy" though which hopefully is fine.
>>> I don't think it's OK to allow the partition to be added if it
>>> contains rows that might not be valid. We are generally vary wary
>>> about adding options that can cause data integrity failures and I
>>> think we shouldn't start here, either. On the other hand, it's also
>>> not desirable for adding a partition to take O(n) time in all cases.
>>> So what would be nice is if the new partition could self-certify that
>>> contains no problematic rows by having a constraint that matches the
>>> new partitioning constraint. Then we can skip the scan if that
>>> constraint is already present.
>>
>> I agree that NO VALIDATE is undesirable and it would be better if we could
>> get rid of the same. I want to clarify one thing though: what does it
>> mean for the new partition to have a constraint that *matches* the new
>> partitioning constraint? Does it mean the new partition's constraint
>> *implies* the partitioning constraint? Or as certified by equal()?
>
> Implies would be better, but equal() might be tolerable.
I think a equal() -based method would fail to help in most cases. Consider
the following example:
create table foo1 (a int);
alter table foo add constraint check_a check (a < 10 and a >= 1);
create table foo (a int) partition by range (a);
alter table foo attach partition foo1 for values from (1) to (10);
The last command will internally generate the partition constraints that
is basically a list of implicitly AND'd expressions viz. a is not null, a
>= 1 and a < 10 which we wrap into a BoolExpr. It would not be equal()
with foo1's constraint even though they are basically the same constraint.
So, simple structural equality may prove to be less productive, IMHO.
It seems a *implies* -based solution would work much better, although a
bit slower for obvious reasons. I reckon slownsess is not a big issue in
this case. So I prototyped the same using predicate_implied_by() which
seems to work reasonably. Looks something like this:
skip_validate = false;
if (predicate_implied_by(partConstraint, existConstraint))
skip_validate = true;
Where partConstraint is 1-member list with the new partition constraint
and existConstraint is a list of the existing constraints of the table
being attached, derived from its TupleConstr.
>>> + /*
>>> + * Never put a
>>> null into the values array, flag
>>> + * instead for
>>> the code further down below where
>>> + * we
>>> construct the actual relcache struct.
>>> + */
>>> +
>>> found_null_partition = true;
>>> +
>>> null_partition_index = i;
>>>
>>> How about we elog(ERROR, ...) if found_null_partition is already set?
>>
>> Makes sense. However, let me mention here that duplicates either within
>> one partition's list or across the partitions are not possible. That's
>> because in case of the former, we de-duplicate before storing the list
>> into the catalog and the latter would simply be an overlap error. Could
>> this be made an Assert() then?
>
> Well, I think that wouldn't be as good, because for example some
> misguided person might do a manual update of the catalog. It seems to
> me that if the system catalog contents are questionable, it's better
> to error out than to have completely undefined behavior. In other
> words, we probably want it checked in non-Assert builds. See similar
> cases where we have things like:
>
> elog(ERROR, "conpfeqop is not a 1-D Oid array");
Good point, agreed.
>>> + /*
>>> + * Is lower_val = upper_val?
>>> + */
>>> + if (lower_val && upper_val)
>>>
>>> So, that comment does not actually match that code. That if-test is
>>> just checking whether both bounds are finite. What I think you should
>>> be explaining here is that if lower_val and upper_val are both
>>> non-infinite, and if the happen to be equal, we want to emit an
>>> equality test rather than a >= test plus a <= test because $REASON.
>>
>> OK, I have added some explanatory comments around this code.
>
> So, I was kind of assuming that the answer to "why are we doing this"
> was "for efficiency". It's true that val >= const AND const <= val
> can be simplified to val = const, but it wouldn't be necessary to do
> so for correctness. It just looks nicer and runs faster.
I guess I ended up with this code following along a bit different way of
thinking about it, but in the end what you're saying is true.
> By the way, if you want to keep the inclusive stuff, I think you
> probably need to consider this logic in light of the possibility that
> one bound might be exclusive. Maybe you're thinking that can't happen
> anyway because it would have been rejected earlier, but an elog(ERROR,
> ...) might still be appropriate just to guard against messed-up
> catalogs.
Yes, that makes sense. I guess you mean a case like the one shown below:
create table foo5 partition of foo for values start (3, 10) end (3, 10);
ERROR: cannot create range partition with empty range
I think the following would suffice as a guard (checked only if it turns
out that lower_val and upper_val are indeed equal):
if (i == key->partnatts - 1 && spec->lowerinc != spec->upperinc)
elog(ERROR, "invalid range bound specification");
>>> What happens if you try to attach a table as a partition of itself or
>>> one of its ancestors?
>>
>> The latter fails with "already a partition" error.
>>
>> The former case was not being handled at all which has now been fixed.
>> ATExecAddInherit() prevents that case as an instance of preventing the
>> circularity of inheritance. It says: ERROR: circular inheritance not
>> allowed. And then: DETAIL: "rel" is already a child of "rel".
>>
>> Should ATExecAttachPartition() use the same trick and keep using the same
>> message(s)? The above detail message doesn't quite sound appropriate when
>> one tries to attach a table as partition of itself.
>
> Whichever way is easier to code seems fine. It's a pretty obscure
> case, so I don't think we need to add code just to get a very slightly
> better error message.
OK, so let's keep it same as with regular inheritance.
>>> + | FOR VALUES START '(' range_datum_list ')' lb_inc
>>> + END_P '('
>>> range_datum_list ')' ub_inc
>>>
>>> Just a random idea. Would for VALUES FROM ( ... ) TO ( ... ) be more
>>> idiomatic than START and END?
>>
>> It would actually. Should I go ahead and change to FROM (...) TO (...)?
>
> +1!
Done!
>> Related to range partitioning, should we finalize on inclusive START/FROM
>> and exclusive END/TO preventing explicit specification of the inclusivity?
>
> I would be in favor of committing the initial patch set without that,
> and then considering the possibility of adding it later. If we
> include it in the initial patch set we are stuck with it.
OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
with each of the range bounds.
I haven't changed any code (such as comparison functions) that manipulates
instances of PartitionRangeBound which has a flag called inclusive. I
didn't remove the flag, but is instead just set to (is_lower ? true :
false) when initializing from the parse node. Perhaps, there is some scope
for further simplifying that code, which you probably alluded to when you
proposed that we do this.
>> Attached updated patches take care of the above comments and few other
>> fixes. There are still a few items I have not addressed right away:
>>
>> - Remove NO VALIDATE clause in ATTACH PARTITION and instead rely on the
>> new partition's constraints to skip the validation scan
>> - Remove the syntax to specify inclusivity of each of START and END bounds
>> of range partitions and instead assume inclusive START and exclusive END
>
> OK - what is the time frame for these changes?
I have implemented both of these in the attached patch. As mentioned
above, the logic to skip the validation scan using the new partition's
constraints is still kind of a prototype solution, but it seems to work so
far. Comments on the same would be very helpful.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-12.patch (114.3K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-12.patch)
download | inline diff:
From f93e6709a6c388b023ba7c918b4e3669c3fdf705 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.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 166 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 24 +-
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 | 450 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 ++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 10 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 268 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 3 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 29 ++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 68 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 43 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 29 ++
src/test/regress/sql/create_table.sql | 143 +++++++++
51 files changed, 1948 insertions(+), 54 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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 part of the 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, 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>partcollation</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
+ the collation to use for partitioning.
+ </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>partattrs</>. 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..6e1ede8 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,41 @@ 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</literal> clause specifies a method
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partition key</firstterm>
+ for the table. When using range partitioning, the partition key can
+ include multiple columns or expressions, but for list partitioning, the
+ partition key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions),
+ which are created using separate <literal>CREATE TABLE</> commands.
+ The partitioned table is itself empty. A data row inserted into the
+ table is routed to a partition based on the value of columns or
+ expressions in the partition key. If no existing partition matches
+ the values in the new row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * StorePartitionKey
+ * Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition attribute numbers, opclass OIDs into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references, which will depend on the table itself; there is no
+ * separate partition key object.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ /*
+ * We must invalidate the relcache so that the next
+ * CommandCounterIncrement() will cause the same to be rebuilt using the
+ * information in just created catalog entry.
+ */
+ CacheInvalidateRelcache(rel);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b4140eb..0ef590a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Identify the opclass to use.
*/
- classOidP[attn] = GetIndexOpClass(attribute->opclass,
- atttype,
- accessMethodName,
- accessMethodId);
+ classOidP[attn] = ResolveOpClass(attribute->opclass,
+ atttype,
+ accessMethodName,
+ accessMethodId);
/*
* Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
*/
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
rel = relation_open(relationId, AccessExclusiveLock);
/*
+ * Process the partitioning specification (if any) and store the
+ * partition key information into the catalog.
+ */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
+ /*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression. It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false. That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (attnum == partattno)
+ {
+ if (used_in_expr)
+ *used_in_expr = false;
+ return true;
+ }
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+
+ /* Find all attributes referenced */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ {
+ if (used_in_expr)
+ *used_in_expr = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5842,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 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in the partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference partitioned table \"%s\"",
+ RelationGetRelationName(pkrel))));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in the partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partitioning strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears more than once in partition key",
+ pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ /* Simple attribute reference */
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+
+ /* Note that whole-row references can't happen here; see below */
+ }
+ else
+ {
+ /* Expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ Bitmapset *expr_attrs = NULL;
+
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+
+ /* Cannot have expressions containing whole-row references */
+ pull_varattnos(expr, 1, &expr_attrs);
+ if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition key expressions cannot contain whole-row references")));
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = ResolveOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
case T_TriggerTransition:
retval = _equalTriggerTransition(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition:
_outTriggerTransition(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -545,6 +547,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..a766835 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partition key */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of columns in the
+ * partition key */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* list of expressions in the partitioning
+ * key; one item for each zero entry in
+ * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9b600a5..ff5ea81 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy; /* partitioning strategy ('list' or 'range') */
+ List *partParams; /* List of PartitionElems */
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1768,6 +1796,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index c867ebb..981d19e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partition key */
+ AttrNumber *partattrs; /* attribute numbers of columns in the
+ * partition key */
+ List *partexprs; /* list of expressions in the partitioning
+ * key, or NIL */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation per attribute */
+ Oid *partcollation;
+
+ /* Type information per attribute */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -533,6 +565,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
c4 | integer | | |
DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR: cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR: cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR: cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+ERROR: column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+ERROR: partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table partitioned depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR: cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-12.patch (23.9K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-12.patch)
download | inline diff:
From 5b6ab1f889d8ee35414dbebcb954ea5ec225f9fe 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 | 159 ++++++++++++++++++++++++++++
src/bin/pg_dump/common.c | 4 +
src/bin/pg_dump/pg_dump.c | 66 +++++++++++-
src/bin/pg_dump/pg_dump.h | 2 +
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 | 20 ++++-
src/test/regress/sql/create_table.sql | 6 +-
10 files changed, 301 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 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_table.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,
@@ -1412,6 +1414,163 @@ 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 opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ oidvector *partcollation;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass and partcollation the hard way */
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partcollation, &isnull);
+ Assert(!isnull);
+ partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+ /*
+ * Get the 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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relid), relid);
+
+ initStringInfo(&buf);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+ Oid keycolcollation;
+ Oid partcoll;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple attribute reference */
+ char *attname;
+ int32 keycoltypmod;
+
+ attname = get_relid_attribute_name(relid, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ get_atttypetypmodcoll(relid, attnum,
+ &keycoltype, &keycoltypmod,
+ &keycolcollation);
+ }
+ else
+ {
+ /* 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, str);
+ keycoltype = exprType(partkey);
+ keycolcollation = exprCollation(partkey);
+ }
+
+ /* Add collation, if not default for column */
+ partcoll = partcollation->values[keyno];
+ if (OidIsValid(partcoll) && partcoll != keycolcollation)
+ appendStringInfo(&buf, " COLLATE %s",
+ generate_collation_name((partcoll)));
+
+ /* 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/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading policies\n");
getPolicies(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading partition key information for interesting tables\n");
+ getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..c443735 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4946,7 +4950,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,
@@ -4960,7 +4964,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5488,7 +5493,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6897,6 +6904,47 @@ getTransforms(Archive *fout, int *numTransforms)
}
/*
+ * getTablePartitionKeyInfo -
+ * for each interesting partitioned table, read information about its
+ * partition key
+ *
+ * modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ int i,
+ ntups;
+ PGresult *res;
+
+ /* No partitioned tables before 10 */
+ if (fout->remoteVersion < 100000)
+ return;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &(tblinfo[i]);
+
+ /* Only partitioned tables have partition key */
+ if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ resetPQExpBuffer(q);
+ appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+ Assert(ntups == 1);
+ tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+ }
+}
+
+/*
* getTableAttrs -
* for each interesting table, read info about its attributes
* (names, types, default values, CHECK constraints, etc)
@@ -14249,6 +14297,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -14309,6 +14360,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14379,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -14544,6 +14597,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 a60cf95..527d6ad 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.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
#endif /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1566,8 +1568,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)");
}
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,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')
{
headers[cols++] = gettext_noop("Collation");
headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,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");
}
@@ -1748,7 +1758,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);
@@ -1756,14 +1766,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 (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 */
@@ -1902,7 +1931,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;
@@ -2461,7 +2490,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;
@@ -2665,7 +2694,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
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3048,7 +3079,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 b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,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 */
@@ -483,7 +483,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 */
@@ -513,7 +513,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 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,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 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,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 e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
ERROR: cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+ Table "public.partitioned"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+ d | text | | |
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+ Table "public.partitioned2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST ((a + 1))
+
DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-12.patch (205.4K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-12.patch)
download | inline diff:
From 2ce3fe1bbf33d3a5a97fc2bc6bbf8daeb88633dd 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.
pg_class gets new fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 103 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 26 +
doc/src/sgml/ref/create_table.sgml | 92 ++-
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 105 ++-
src/backend/catalog/partition.c | 1674 ++++++++++++++++++++++++++++
src/backend/commands/createas.c | 2 +-
src/backend/commands/sequence.c | 2 +-
src/backend/commands/tablecmds.c | 1001 ++++++++++++++---
src/backend/commands/typecmds.c | 3 +-
src/backend/commands/view.c | 3 +-
src/backend/nodes/copyfuncs.c | 47 +
src/backend/nodes/equalfuncs.c | 41 +
src/backend/nodes/nodeFuncs.c | 6 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 34 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_utilcmd.c | 260 +++++-
src/backend/tcop/utility.c | 6 +-
src/backend/utils/cache/relcache.c | 93 ++-
src/include/catalog/heap.h | 1 +
src/include/catalog/partition.h | 54 +
src/include/catalog/pg_class.h | 22 +-
src/include/commands/tablecmds.h | 2 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 52 +-
src/include/parser/kwlist.h | 2 +
src/include/parser/parse_utilcmd.h | 2 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 240 ++++
src/test/regress/expected/create_table.out | 187 ++++
src/test/regress/sql/alter_table.sql | 200 ++++
src/test/regress/sql/create_table.sql | 153 +++
34 files changed, 4523 insertions(+), 168 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6139ab1..31352a2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..3c2cb0e 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
+ DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,51 @@ 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 table (which might itself be partitioned)
+ as a partition of the target table. The partition bound specification
+ must correspond to the partitioning method and partition key of the
+ target table. The table to be attached must have all the same columns
+ as the target table and no more; moreover, the column types must also
+ match. Also, it must have all the <literal>NOT NULL</literal> and
+ <literal>CHECK</literal> constraints present in the target table.
+ If some <literal>CHECK</literal> constraint of the table being attached
+ is marked <literal>NO INHERIT</literal>, the command will fail; such
+ constraints must be recreated without the <literal>NO INHERIT</literal>
+ clause. Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+ and <literal>FOREIGN KEY</literal> constraints are not considered.
+ </para>
+
+ <para>
+ A full table scan is performed on the table being attached to check that
+ no existing row in the table violates the partition constraint. It is
+ possible to avoid this potentially expensive scan by adding a valid
+ <literal>CHECK</literal> constraint to the table beforehand that only
+ allows rows satisfying the desired partition constraint. In this case,
+ it will be determined using such a constraint that existing rows in the
+ table satisfies the partition constraint, so the table scan to check the
+ same will be skipped. In case of a range partition, it is advised to
+ add a <literal>NOT NULL</literal> constraint to each partition key
+ column, because <literal>NULL</literal> values are disallowed in the
+ partition key columns when using range partitioning.
+ </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, but no longer has any
+ ties to the table from which it was detached.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -721,8 +773,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>.
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.
+ To add the table as a new child of a parent table, or as a new partition
+ of a partitioned table, you must own the parent table as well.
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
@@ -938,6 +990,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 or to 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>
@@ -978,6 +1048,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</para>
<para>
+ Similarly, when attaching a new partition it is scanned to verify that
+ existing rows meet the partition constraint.
+ </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.
@@ -1047,6 +1122,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited.
+ A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+ partitioned table, because all partitions of a table must have the same
+ columns as the partitioning root.
</para>
<para>
@@ -1233,6 +1311,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 FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+ <para>
+ Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+ ATTACH PARTITION cities_west FOR VALUES IN ('los angeles', 'san fransisco');
+</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..5d0dcf5 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,12 @@ 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.
+ </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 +329,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</>, as a partition
+ of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+ PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('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 6e1ede8..b79a8ee 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
<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>] [, ... ] ) ]
@@ -232,6 +247,50 @@ 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.
+ </para>
+
+ <para>
+ The partition bound specification must correspond to the partitioning
+ method and partition key of the parent table, and must not overlap with
+ any existing partition of that 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.
+ Defaults and constraints can optionally be specified for each of the
+ inherited columns. One can also specify table constraints in addition
+ to those inherited from the parent. If a check constraint with the name
+ matching one of the parent's constraint is specified, it is merged with
+ the latter, provided the specified condition is same.
+ </para>
+
+ <para>
+ Rows inserted into a partitioned table will be automatically routed to
+ the correct partition. If no suitable partition exists, an error will
+ occur.
+ </para>
+
+ <para>
+ A partition must have the same column names and types as the table of
+ which it is a partition. Therefore, modifications to the column names
+ or types of the partitioned table will automatically propagate to all
+ children, as will operations such as TRUNCATE which normally affect a
+ table and all of its inheritance children. It is also possible to
+ TRUNCATE a partition individually, just as for an inheritance child.
+ Dropping a partition directly using <literal>DROP TABLE</literal>
+ will fail; it must be <firstterm>detached</> from the parent first.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
CREATE TABLE cities (
name text not null,
population int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(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 FROM ('2016-07-01') TO ('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 Francisco');
+</programlisting></para>
+
+ <para>
+ Create partition of a list partitioned table that is itself further
+ partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+ PARTITION OF cities (
+ CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+ PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
</programlisting></para>
</refsect1>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 754a08b..64fc283 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"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ /* relpartbound is set by updating this tuple, if necessary */
+ nulls[Anum_pg_class_relpartbound - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ /* relispartition is always set by updating this tuple later */
+ new_rel_reltup->relispartition = false;
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
heap_drop_with_catalog(Oid relid)
{
Relation rel;
+ Oid parentOid;
+ Relation parent = NULL;
/*
* Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
rel = relation_open(relid, AccessExclusiveLock);
/*
+ * If the relation is a partition, we must grab exclusive lock on its
+ * parent because we need to update its partition descriptor. We must
+ * take a table lock strong enough to prevent all queries on the parent
+ * from proceeding until we commit and send out a shared-cache-inval
+ * notice that will make them update their partition descriptor.
+ * Sometimes, doing this is cycles spent uselessly, especially if the
+ * parent will be dropped as part of the same command anyway.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ parentOid = get_partition_parent(relid);
+ parent = heap_open(parentOid, AccessExclusiveLock);
+ }
+
+ /*
* There can no longer be anyone *else* touching the relation, but we
* might still have open queries or cursors, or pending trigger events, in
* our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
* delete relation tuple
*/
DeleteRelationTuple(relid);
+
+ if (parent)
+ {
+ CacheInvalidateRelcache(parent);
+ heap_close(parent, NoLock); /* keep the lock */
+ }
}
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
* definition) then interpret addition of a local constraint as a
* legal merge. This allows ALTER ADD CONSTRAINT on parent and
* child tables to be given in either order with same end state.
+ * However if the relation is a partition, all inherited
+ * constraints are always non-local, including those that were
+ * merged.
*/
- if (is_local && !con->conislocal)
+ if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
allow_merge = true;
if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
tup = heap_copytuple(tup);
con = (Form_pg_constraint) GETSTRUCT(tup);
- if (is_local)
- con->conislocal = true;
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ con->coninhcount = 1;
+ con->conislocal = false;
+ }
else
- con->coninhcount++;
+ {
+ if (is_local)
+ con->conislocal = true;
+ else
+ con->coninhcount++;
+ }
+
if (is_no_inherit)
{
Assert(is_local);
@@ -3176,3 +3224,52 @@ RemovePartitionKeyByRelId(Oid relid)
ReleaseSysCache(tuple);
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * StorePartitionBound
+ * Update pg_class tuple of rel to store the partition bound and set
+ * relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+ Relation classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+ {
+ Form_pg_class classForm;
+ bool isnull;
+
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(!classForm->relispartition);
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+ }
+#endif
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..97cb9a4
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1674 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; has
+ * same length as the values array above */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ int index;
+ Datum *datums; /* range bound datums */
+ bool *infinite; /* infinite flag for each datum */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, and we only store one of them.
+ */
+typedef struct PartitionRangeInfo
+{
+ PartitionRangeBound **bounds;
+ int nbounds;
+ int *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation (either physical or
+ * logical)
+ */
+typedef struct BoundCollectionData
+{
+ char strategy; /* list or range bounds? */
+
+ union
+ {
+ struct PartitionListInfo lists;
+ struct PartitionRangeInfo ranges;
+ } bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+ Datum value;
+ int index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+ PartitionRangeBound *,
+ void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *inhoids,
+ *partoids;
+ Oid *oids = NULL;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ PartitionListValue **all_values = NULL;
+ int all_values_count = 0;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ PartitionRangeBound **distinct_bounds = NULL;
+ int num_distinct_bounds = 0;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ partoids = NIL;
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+
+ /*
+ * It is possible that the pg_class tuple of a partition has not been
+ * updated yet to set its relpartbound field. The only case where
+ * this happens is when we open the parent relation to check using its
+ * partition descriptor that a new partition's bound does not overlap
+ * some existing partition. XXX - perhaps it would be better if we
+ * did not need to use the parent relation desc in that case, because
+ * the following looks kind of ugly.
+ */
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+ {
+ ReleaseSysCache(tuple);
+ continue;
+ }
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ partoids = lappend_oid(partoids, inhrelid);
+ }
+
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc(nparts * sizeof(Oid));
+ i = 0;
+ foreach (cell, partoids)
+ oids[i++] = lfirst_oid(cell);
+
+ /* Convert from node to the internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ List *non_null_values = NIL;
+
+ /*
+ * Create a unified list of non-null values across all
+ * partitions.
+ */
+ i = 0;
+ found_null_partition = false;
+ null_partition_index = -1;
+ foreach(cell, boundspecs)
+ {
+ ListCell *c;
+ PartitionBoundSpec *spec = lfirst(cell);
+
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ foreach (c, spec->listdatums)
+ {
+ Const *val = lfirst(c);
+ PartitionListValue *list_value = NULL;
+
+ if (!val->constisnull)
+ {
+ list_value = (PartitionListValue *)
+ palloc0(sizeof(PartitionListValue));
+ list_value->index = i;
+ list_value->value = val->constvalue;
+ }
+ else
+ {
+ /*
+ * Never put a null into the values array, flag
+ * instead for the code further down below where
+ * we construct the actual relcache struct.
+ */
+ if (found_null_partition)
+ elog(ERROR, "found null more than once");
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+
+ if (list_value)
+ non_null_values = lappend(non_null_values,
+ list_value);
+ }
+
+ i++;
+ }
+
+ all_values_count = list_length(non_null_values);
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (PartitionListValue **)
+ palloc(all_values_count *
+ sizeof(PartitionListValue *));
+ i = 0;
+ foreach(cell, non_null_values)
+ {
+ PartitionListValue *src = lfirst(cell);
+
+ all_values[i] = (PartitionListValue *)
+ palloc(sizeof(PartitionListValue));
+ all_values[i]->value = src->value;
+ all_values[i]->index = src->index;
+ i++;
+ }
+
+ qsort_arg(all_values, all_values_count,
+ sizeof(PartitionListValue *),
+ qsort_partition_list_value_cmp,
+ (void *) key);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ int j, k;
+ PartitionRangeBound **all_bounds,
+ *prev;
+
+ all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+ sizeof(PartitionRangeBound *));
+
+ /*
+ * Create a unified list of range bounds across all the
+ * partitions.
+ */
+ i = j = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundSpec *spec = lfirst(cell);
+ PartitionRangeBound *lower, *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, i, spec->lowerdatums,
+ true);
+ /*
+ * In most cases, upper bound of the i'th partition is
+ * equal in *value* to the lower bound of the (i+1)'th
+ * partition, execpt they have different inclusivity.
+ * We want to keep only the former in the final list of
+ * bounds by eliminating the latter. To be able to equate
+ * the two, we flip the inclusivity flag of the latter,
+ * so that it matches the former's flag value in exactly
+ * the cases where the elimination must occur.
+ */
+ lower->inclusive = !lower->inclusive;
+
+ upper = make_one_range_bound(key, i, spec->upperdatums,
+ false);
+ all_bounds[j] = lower;
+ all_bounds[j+1] = upper;
+ j += 2;
+ i++;
+ }
+ Assert(j == 2 * nparts);
+
+ /* Sort all the bounds in ascending order */
+ qsort_arg(all_bounds, 2 * nparts,
+ sizeof(PartitionRangeBound *),
+ qsort_partition_rbound_cmp,
+ (void *) key);
+ /*
+ * Count the number of distinct bounds to allocate an array
+ * of that size.
+ */
+ num_distinct_bounds = 0;
+ prev = NULL;
+ for (i = 0; i < j; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ /*
+ * Count the current bound if it is distinct from the
+ * previous one.
+ */
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ num_distinct_bounds++;
+
+ prev = cur;
+ }
+
+ /*
+ * Finally save them in an array from where they will be
+ * copied into the relcache.
+ */
+ distinct_bounds = (PartitionRangeBound **)
+ palloc0(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ k = 0;
+ prev = NULL;
+ for (i = 0; i < j; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ distinct_bounds[k++] = cur;
+
+ /*
+ * If it's a lower bound, flip the inclusive flag to its
+ * original value.
+ */
+ if (cur->lower)
+ cur->inclusive = !cur->inclusive;
+
+ prev = cur;
+ }
+ Assert(k == num_distinct_bounds);
+
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ int *mapping;
+ int next_index = 0;
+
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->boundinfo = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ result->boundinfo->strategy = key->strategy;
+
+ /* Allocate and initialize with invalid mapping values */
+ mapping = (int *) palloc(sizeof(int) * nparts);
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionListInfo listinfo;
+
+ listinfo.nvalues = all_values_count;
+ listinfo.has_null = found_null_partition;
+ listinfo.values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo.indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values and indexes. While copying the indexes, adjust
+ * them so that they match for any two list partitioned tables
+ * with same number of partitions and same lists per partition.
+ * One way to achieve this is to assign indexes in the same
+ * order as the least values of the individual lists.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo.values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+
+ /* If this index has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo.indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * Assign a valid mapping to NULL-accepting partition, if not
+ * already done (could happen if such partition accepts only
+ * NULL and hence not covered in the above loop)
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo.null_index = mapping[null_partition_index];
+ else
+ listinfo.null_index = -1;
+
+ result->boundinfo->bounds.lists = listinfo;
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeInfo rangeinfo;
+
+ rangeinfo.bounds = (PartitionRangeBound **)
+ palloc(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ rangeinfo.indexes = (int *)
+ palloc((num_distinct_bounds + 1) *
+ sizeof(int));
+
+ for (i = 0; i < num_distinct_bounds; i++)
+ {
+ rangeinfo.bounds[i] =
+ copy_range_bound(key, distinct_bounds[i]);
+
+ /* There is no mapping for invalid indexes (ie, -1) */
+ if (rangeinfo.bounds[i]->lower)
+ rangeinfo.indexes[i] = -1;
+ else
+ {
+ int orig_index = rangeinfo.bounds[i]->index;
+
+ /* If this index has no mapping, assign one */
+ if (mapping[orig_index] == -1)
+ mapping[orig_index] = next_index++;
+
+ rangeinfo.indexes[i] = mapping[orig_index];
+ }
+ }
+ rangeinfo.indexes[i] = -1;
+ rangeinfo.nbounds = num_distinct_bounds;
+
+ result->boundinfo->bounds.ranges = rangeinfo;
+ break;
+ }
+ }
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of the
+ * list values or the range bounds (ie, sorted).
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+ pfree(mapping);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2)
+{
+ if (b1->strategy != b2->strategy)
+ return false;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key,
+ &b1->bounds.lists, &b2->bounds.lists))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key,
+ &b1->bounds.ranges, &b2->bounds.ranges))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent. Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+ bool overlap = false;
+
+ parent = heap_open(parentId, NoLock); /* already locked */
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ if (pdesc->nparts > 0)
+ {
+ ListCell *cell;
+ bool new_has_null = false;
+ PartitionListInfo listinfo;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+ Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+ /* If the new partition's list contains null, set a flag. */
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ {
+ new_has_null = true;
+ break;
+ }
+ }
+
+ /*
+ * Now check against listinfo whether the new partition's
+ * values are not already taken by existing partitions.
+ */
+ if (new_has_null && listinfo.has_null)
+ {
+ with = listinfo.null_index;
+ overlap = true;
+ }
+ else
+ {
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+ int found = -1;
+
+ /* bsearch a new list's value in listinfo->values */
+ if (!val->constisnull)
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ val->constvalue);
+ if (found >= 0)
+ {
+ overlap = true;
+ with = listinfo.indexes[found];
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeBound *lower,
+ *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+ upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+ /*
+ * First check if the resulting range would be empty with
+ * specified bounds
+ */
+ if (partition_rbound_cmp(key, lower, upper) >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, spec->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ PartitionRangeInfo rangeinfo;
+ int offset1, offset2;
+ bool equal = false;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ /*
+ * Find the greatest index of a range bound that is less
+ * than the new lower bound.
+ */
+ offset1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, lower,
+ partition_rbound_cmp, false, &equal);
+
+ /*
+ * If equal has been set to true, that means the new bound is
+ * found to be equal to some existing bound. offset1 == -1
+ * means that all existing range bounds are greater than the
+ * new bound. If offset >= 0, and there exists a gap between
+ * the corresponding bound and the next, new partition can
+ * still manage to fit within the same. To confirm whether it
+ * is so, check the new upper bound.
+ */
+ if (!equal &&
+ (offset1 < 0 || rangeinfo.indexes[offset1+1] < 0))
+ {
+ offset2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, upper,
+ partition_rbound_cmp, false,
+ &equal);
+
+ if (equal || offset1 != offset2)
+ {
+ overlap = true;
+ with = rangeinfo.indexes[offset2+1];
+ }
+ }
+ else
+ {
+ overlap = true;
+ if (offset1 == -1)
+ {
+ Assert(equal);
+ offset1 = 0;
+ }
+ with = rangeinfo.indexes[offset1+1];
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (overlap)
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, spec->location)));
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition check constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual = NIL;
+ TupleDesc parent_tupdesc = RelationGetDescr(parent);
+ AttrNumber parent_attno;
+ AttrNumber *partition_attnos;
+ bool found_whole_row;
+
+ Assert(key != NULL);
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, spec);
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, spec);
+ break;
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partition_attnos = (AttrNumber *)
+ palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+ for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+ parent_attno++)
+ {
+ Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+ char *attname = NameStr(attribute->attname);
+ AttrNumber partition_attno;
+
+ if (attribute->attisdropped)
+ continue;
+
+ partition_attno = get_attnum(RelationGetRelid(rel), attname);
+ partition_attnos[parent_attno - 1] = partition_attno;
+ }
+
+ my_qual = (List *) map_variable_attnos((Node *) my_qual,
+ 1, 0,
+ partition_attnos,
+ parent_tupdesc->natts,
+ &found_whole_row);
+ /* there can never be a whole-row reference in there*/
+ Assert(!found_whole_row);
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result;
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If the list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, remove the null Const from the list and create a IS NULL
+ * test and create an OR expression along with ScalarArrayOpExpr. We do
+ * it this way because the null-valued expression does not have the
+ * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(spec->listdatums); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ spec->listdatums = list_delete_cell(spec->listdatums,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = spec->listdatums;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ if (nulltest1)
+ result = list_make2(nulltest1, opexpr);
+ else if (nulltest2)
+ {
+ Expr *or;
+
+ or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+ result = list_make1(or);
+ }
+ else
+ result = list_make1(opexpr);
+
+ return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+
+ /*
+ * Iterate over columns of the key, emitting an OpExpr for each using
+ * the corresponding lower and upper datums as constant operands.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum = lfirst(cell1),
+ *udatum = lfirst(cell2);
+ Node *key_col;
+ Const *lower_val = NULL,
+ *upper_val = NULL;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+ Oid operoid;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Stop at this column if either of lower or upper datum is infinite,
+ * but do emit an OpExpr for the non-infinite datum.
+ */
+ if (!ldatum->infinite)
+ lower_val = (Const *) ldatum->value;
+ if (!udatum->infinite)
+ upper_val = (Const *) udatum->value;
+
+ /*
+ * If lower_val and upper_val are both finite and happen to be equal,
+ * emit only (key_col = lower_val) for this column, because all rows
+ * in this partition could only ever contain this value (ie, lower_val)
+ * in the current partitioning column. We must consider further
+ * columns because the above condition does not fully constrain the
+ * rows of this partition.
+ */
+ if (lower_val && upper_val)
+ {
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ /* Create the test expression */
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /* This can never be, but it's better to make sure */
+ if (i == key->partnatts - 1)
+ elog(ERROR, "invalid range bound specification");
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go over to consider the next column. */
+ i++;
+ continue;
+ }
+ }
+
+ /*
+ * We can say here that lower_val <> upper_val. Emit expressions
+ * (key_col >= lower_val) and (key_col < upper_val), then stop.
+ */
+ if (lower_val)
+ {
+ operoid = get_partition_operator(key, i,
+ BTGreaterEqualStrategyNumber,
+ &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ if (upper_val)
+ {
+ operoid = get_partition_operator(key, i,
+ BTLessStrategyNumber,
+ &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ /*
+ * We can stop at this column, because we would not have checked
+ * the next column when routing a given row into this partition.
+ */
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ /*
+ * First check if there exists an operator of the given strategy, with
+ * this column's type as both its lefttype and righttype, in the
+ * partitioning operator family specified for the column.
+ */
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+
+ /*
+ * If one doesn't exist, we must resort to using an operator in the same
+ * opreator family but with the operator class declared input type. It is
+ * OK to do so, because the column's type is known to be binary-coercible
+ * with the operator class input type (otherwise, the operator class in
+ * question would not have been accepted as the partitioning operator
+ * class). We must however inform the caller to wrap the non-Const
+ * expression with a RelabelType node to denote the implicit coercion. It
+ * ensures that the resulting expression structurally matches similarly
+ * processed expressions within the optimizer.
+ */
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+ else
+ *need_relabel = false;
+
+ if (!OidIsValid(operoid))
+ elog(ERROR, "could not find operator for partitioning");
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Get pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+ ReleaseSysCache(tuple);
+
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Keep the parent locked until commit */
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ /* Make sure that the indexes have been "canonicalized" */
+ Assert(l1->indexes[i] == l2->indexes[i]);
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+ Datum value1 = (*(const PartitionListValue **) a)->value,
+ value2 = (*(const PartitionListValue **) b)->value;
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = partition_list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+ int i;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->index = index;
+ bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+ bound->inclusive = lower;
+ bound->lower = lower;
+
+ i = 0;
+ foreach (cell, datums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+
+ bound->infinite[i] = datum->infinite;
+
+ if (!bound->infinite[i])
+ {
+ Const *val = (Const *) datum->value;
+
+ if (val->constisnull)
+ elog(ERROR, "invalid range bound datum");
+ bound->datums[i] = val->constvalue;
+ }
+
+ i++;
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->index = src->index;
+ result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+ result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ for (i = 0; i < partnatts; i++)
+ {
+ result->infinite[i] = src->infinite[i];
+ if (!result->infinite[i])
+ result->datums[i] = datumCopy(src->datums[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+ int i;
+
+ if (r1->nbounds != r2->nbounds)
+ return false;
+
+ /* Compare each individual range's lower and upper bounds */
+ for (i = 0; i < r1->nbounds; i++)
+ {
+ if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+ return false;
+
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+ }
+
+ /* There is one more index than bounds */
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+
+ return true;
+}
+
+/*
+ * Return for two range bounds whether b1 <=, =, >= b2
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+ PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int32 cmpval;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ /*
+ * First, handle cases involving infinity, which don't require
+ * invoking the comparison proc.
+ */
+ if (b1->infinite[i] && b2->infinite[i])
+ {
+ /*
+ * 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[i])
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite[i])
+ return b2->lower ? 1 : -1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /*
+ * 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 (cmpval == 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 cmpval;
+}
+
+/*
+ * Are two (consecutive) range bounds equal without distinguishing lower
+ * and upper?
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ int32 cmpval = 0;
+
+ /*
+ * If either of them has infinite element, we can't equate them.
+ * Even if both were infinite, they'd have opposite signs (it always
+ * holds that b1 and b2 are lower and upper bound or vice versa).
+ */
+ if (b1->infinite[i] || b2->infinite[i])
+ return false;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ return false;
+ }
+
+ /*
+ * All datums are found to be equal, now only if both are inclusive
+ * or exclusive then they are considered equal.
+ */
+ if (b1->inclusive == b2->inclusive)
+ return true;
+
+ return false;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+ PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple. The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal)
+{
+ int lo, hi, mid;
+
+ lo = -1;
+ hi = n - 1;
+ while (lo < hi)
+ {
+ PartitionRangeBound *cur;
+ int32 cmpval;
+
+ mid = (lo + hi + 1) / 2;
+ cur = bounds[mid];
+
+ cmpval = cmp(key, cur, probe);
+ if (is_equal)
+ *is_equal = (cmpval == 0);
+
+ if (cmpval < 0 || (equal_allowed && cmpval == 0))
+ lo = mid;
+ else
+ hi = mid - 1;
+ }
+
+ return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
* Create the relation. (This will error out if there's an existing view,
* so we don't need more code to complain if "replace" is false.)
*/
- intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+ intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
/*
* If necessary, create a TOAST table for the target table. Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists;
- address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+ address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
seqoid = address.objectId;
Assert(seqoid != InvalidOid);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1ddf443..395e116 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"
@@ -65,6 +66,8 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
@@ -163,6 +166,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_quals; /* partition check quals for attach partition
+ * validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +284,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -346,7 +352,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse);
static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +452,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -466,7 +479,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
*/
ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress)
+ ObjectAddress *typaddress, const char *queryString)
{
char relname[NAMEDATALEN];
Oid namespaceId;
@@ -597,7 +610,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
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
@@ -619,6 +633,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
+ if (stmt->partbound)
+ {
+ /* If the parent has OIDs, partitions must have them too. */
+ if (parentOidCount > 0 && !localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table without OIDs as partition of table with OIDs")));
+ /* If the parent doesn't, partitions must not have them. */
+ if (parentOidCount == 0 && localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+ }
+
/*
* Find columns with default values and prepare for insertion of the
* defaults. Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition bound, if any. */
+ if (stmt->partbound)
+ {
+ Node *bound;
+ ParseState *pstate;
+ Oid parentId = linitial_oid(inheritOids);
+ Relation parentRel;
+
+ /* Already have strong enough lock on the parent */
+ parentRel = heap_open(parentId, NoLock);
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of parentRel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* Tranform the bound values */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+ bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+ heap_close(parentRel, NoLock);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(rel, bound);
+
+ /*
+ * The code that follows may also update the pg_class tuple to update
+ * relnumchecks, so bump up the command counter to avoid the "already
+ * updated by self" error.
+ */
+ CommandCounterIncrement();
+ }
+
/*
* Process the partitioning specification (if any) and store the
* partition key information into the catalog.
@@ -1096,6 +1169,7 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
+ /* Force inheritance recursion, if partitioned table. */
if (recurse)
{
ListCell *child;
@@ -1117,6 +1191,10 @@ ExecuteTruncate(TruncateStmt *stmt)
relids = lappend_oid(relids, childrelid);
}
}
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("must truncate child tables too")));
}
/*
@@ -1474,7 +1552,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;
@@ -1484,6 +1563,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
+ List *saved_schema;
/*
* Check for and reject tables with too many columns. We perform this
@@ -1508,53 +1588,59 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* Although we might consider merging such entries in the same way that we
* handle name conflicts for inherited attributes, it seems to make more
* sense to assume such conflicts are errors.
+ *
+ * In case of partitions, schema contains only the WITH OPTION entries
+ * at this point, so the following checks are meaningless yet.
*/
- foreach(entry, schema)
+ if (!is_partition)
{
- ColumnDef *coldef = lfirst(entry);
- ListCell *rest = lnext(entry);
- ListCell *prev = entry;
-
- if (coldef->typeName == NULL)
+ foreach(entry, schema)
+ {
+ ColumnDef *coldef = lfirst(entry);
+ ListCell *rest = lnext(entry);
+ ListCell *prev = entry;
/*
* 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.
*/
- ereport(ERROR,
+ if (coldef->typeName == NULL)
+ ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
coldef->colname)));
- while (rest != NULL)
- {
- ColumnDef *restdef = lfirst(rest);
- ListCell *next = lnext(rest); /* need to save it in case we
+ while (rest != NULL)
+ {
+ ColumnDef *restdef = lfirst(rest);
+ ListCell *next = lnext(rest); /* need to save it in case we
* delete it */
- if (strcmp(coldef->colname, restdef->colname) == 0)
- {
- if (coldef->is_from_type)
+ if (strcmp(coldef->colname, restdef->colname) == 0)
{
- /*
- * merge the column options into the column from the type
- */
- 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_from_type = false;
- list_delete_cell(schema, rest, prev);
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_COLUMN),
+ if (coldef->is_from_type)
+ {
+ /*
+ * merge the column options into the column from the
+ * type
+ */
+ 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_from_type = false;
+ list_delete_cell(schema, rest, prev);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" specified more than once",
coldef->colname)));
+ }
+ prev = rest;
+ rest = next;
}
- prev = rest;
- rest = next;
}
}
@@ -1582,18 +1668,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * Cannot inherit from partitioned tables unless the child table is a
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
parent->relname)));
+ /* The same if the parent is a partition */
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1708,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation \"%s\"",
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation \"%s\""
+ : "cannot create as partition of temporary relation \"%s\"",
parent->relname)));
/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1718,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
!relation->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation of another session")));
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation of another session"
+ : "cannot create as partition of temporary relation of another session")));
/*
* We should have an UNDER permission flag for this, but for now,
@@ -1855,6 +1964,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
heap_close(relation, NoLock);
}
+ /* In case of a partition, there are no new column definitions */
+ if (is_partition)
+ {
+ saved_schema = schema;
+ schema = NIL;
+ }
+
/*
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
@@ -1886,6 +2002,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Oid defcollid,
newcollid;
+ Assert(!is_partition);
+
/*
* Yes, try to merge the two column definitions. They must
* have the same type, typmod, and collation.
@@ -1934,7 +2052,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
+ /* Mark the column as locally defined (unless partition) */
def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
@@ -1968,6 +2086,52 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
/*
+ * Now that we have a column definition list for a partition, we can check
+ * whether the referenced column actually exists.
+ */
+ if (is_partition)
+ schema = list_concat(schema, saved_schema);
+
+ foreach(entry, schema)
+ {
+ ColumnDef *coldef = lfirst(entry);
+ ListCell *rest = lnext(entry);
+ ListCell *prev = entry;
+
+ /*
+ * 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.
+ */
+ if (coldef->typeName == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist",
+ coldef->colname)));
+ while (rest != NULL)
+ {
+ ColumnDef *restdef = lfirst(rest);
+ ListCell *next = lnext(rest); /* need to save it in case we
+ * delete it */
+
+ if (strcmp(coldef->colname, restdef->colname) == 0)
+ {
+ /*
+ * 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;
+ list_delete_cell(schema, rest, prev);
+ }
+ prev = rest;
+ rest = next;
+ }
+ }
+
+ /*
* If we found any conflicting parent default values, check to make sure
* they were overridden by the child.
*/
@@ -2455,7 +2619,7 @@ renameatt(RenameStmt *stmt)
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt),
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -3129,6 +3293,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);
@@ -3240,12 +3409,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepDropNotNull(rel, recurse);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepSetNotNull(rel, recurse);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3617,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);
@@ -3516,7 +3693,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, the
+ * following check is unnecessary, because we did not modify anything
+ * about it that will change its toasting requirement.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ !tab->partition_quals) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3765,6 +3949,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);
@@ -3950,7 +4140,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4030,6 +4220,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4094,6 +4285,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_quals)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_quals,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4483,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("partition constraint is violated by some row")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4685,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4802,6 +5008,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5248,6 +5459,19 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be dropped from child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+}
static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
@@ -5323,6 +5547,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
+ /* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
/*
* Okay, actually perform the catalog change ... if needed
*/
@@ -5355,6 +5596,20 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
* Return the address of the modified column. If the column was already NOT
* NULL, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be added to the child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be added to child tables too")));
+}
+
static ObjectAddress
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6169,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
Relation attr_rel;
ListCell *child;
+ /*
+ * In case of a partitioned table, the column must be dropped from the
+ * partitions as well.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column must be dropped from child tables too")));
+
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(child, children)
{
@@ -7916,6 +8180,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
}
/*
+ * In case of a partitioned table, the constraint must be dropped from
+ * the partitions too. There is no such thing as NO INHERIT constraints
+ * in case of partitioned tables.
+ */
+ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+
+ /*
* 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.
@@ -10217,6 +10491,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10508,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;
@@ -10279,37 +10553,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
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);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10592,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);
@@ -10358,16 +10669,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;
}
/*
@@ -10418,7 +10721,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
@@ -10436,12 +10739,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10796,14 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
* later on, this change will just roll back.)
*/
childatt->attinhcount++;
+
+ /*
+ * In case of partitions, we must enforce that value of attislocal
+ * is same in all partitions. (Note: there are only inherited
+ * attributes in partitions)
+ */
+ if (is_attach_partition)
+ childatt->attislocal = false;
simple_heap_update(attrrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrrel, tuple);
heap_freetuple(tuple);
@@ -10511,7 +10826,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.
@@ -10530,10 +10845,14 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_attach_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10610,6 +10929,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (is_attach_partition)
+ {
+ Assert(child_con->coninhcount == 1);
+ child_con->conislocal = false;
+ }
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10634,6 +10965,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10647,13 +11018,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];
@@ -10662,19 +11031,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
-
- /*
- * 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);
+ bool is_detach_partition = false;
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ is_detach_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11044,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);
@@ -10705,11 +11065,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
@@ -10718,7 +11087,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)))
@@ -10780,7 +11149,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);
@@ -10811,7 +11180,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)
@@ -10823,30 +11192,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;
}
/*
@@ -10913,7 +11272,7 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
Oid relid = RelationGetRelid(rel);
Type typetuple;
Oid typeid;
- Relation inheritsRelation,
+ Relation catalogation,
relationRelation;
SysScanDesc scan;
ScanKeyData key;
@@ -10931,19 +11290,19 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
typeid = HeapTupleGetOid(typetuple);
/* Fail if the table has any inheritance parents. */
- inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ catalogation = heap_open(InheritsRelationId, AccessShareLock);
ScanKeyInit(&key,
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
- scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ scan = systable_beginscan(catalogation, InheritsRelidSeqnoIndexId,
true, NULL, 1, &key);
if (HeapTupleIsValid(systable_getnext(scan)))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("typed tables cannot inherit")));
systable_endscan(scan);
- heap_close(inheritsRelation, AccessShareLock);
+ heap_close(catalogation, AccessShareLock);
/*
* Check the tuple descriptors for compatibility. Unlike inheritance, we
@@ -12531,3 +12890,379 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * 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,
+ catalog;
+ List *childrels;
+ TupleConstr *attachRel_constr;
+ List *partConstraint,
+ *existConstraint;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ bool skip_validate = false;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+ /* A partition can only have one parent */
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /*
+ * attachRel should not already be part of inheritance; either as a child
+ * table...
+ */
+ catalog = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+ NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach inheritance child as partition")));
+ systable_endscan(scan);
+
+ /* ...or be a RELKIND_RELATION parent table */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+ 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)) &&
+ attachRel->rd_rel->relkind == RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach regular inheritance parent as partition")));
+ systable_endscan(scan);
+ heap_close(catalog, AccessShareLock);
+
+ /*
+ * Prevent circularity by seeing if rel is a partition of attachRel.
+ * (In particular, this disallows making a rel a partition of itself.)
+ */
+ childrels = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessShareLock, NULL);
+ if (list_member_oid(childrels, RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("circular inheritance not allowed"),
+ errdetail("\"%s\" is already a child of \"%s\".",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(attachRel))));
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("New partition should contain only the columns present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(attachRel, cmd->bound);
+
+ /*
+ * Generate partition constraint from the partition bound specification.
+ * If the parent itself is a partition, make sure to include its
+ * constraint as well.
+ */
+ partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+ cmd->bound),
+ RelationGetPartitionQual(rel, true));
+ partConstraint = (List *) eval_const_expressions(NULL,
+ (Node *) partConstraint);
+ partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+ partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+ /*
+ * Check if we can do away with having to scan the table being attached
+ * to validate the partition constraint by *proving* that the existing
+ * constraints of the table would anyway *imply* the partition constraint.
+ * We include check constraints and NOT NULL constraints in the list of
+ * constraints we'll be using for the proof.
+ */
+ tupleDesc = RelationGetDescr(attachRel);
+ attachRel_constr = tupleDesc->constr;
+ existConstraint = NIL;
+ if (attachRel_constr > 0)
+ {
+ int num_check = attachRel_constr->num_check;
+ int i;
+
+ if (attachRel_constr->has_not_null)
+ {
+ int natts = attachRel->rd_att->natts;
+
+ for (i = 1; i <= natts; i++)
+ {
+ Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+ if (att->attnotnull && !att->attisdropped)
+ {
+ NullTest *ntest = makeNode(NullTest);
+
+ ntest->arg = (Expr *) makeVar(1,
+ i,
+ att->atttypid,
+ att->atttypmod,
+ att->attcollation,
+ 0);
+ ntest->nulltesttype = IS_NOT_NULL;
+
+ /*
+ * argisrow=false is correct even for a composite column,
+ * because attnotnull does not represent a SQL-spec IS NOT
+ * NULL test in such a case, just IS DISTINCT FROM NULL.
+ */
+ ntest->argisrow = false;
+ ntest->location = -1;
+ existConstraint = lappend(existConstraint, ntest);
+ }
+ }
+ }
+
+ for (i = 0; i < num_check; i++)
+ {
+ Node *cexpr;
+
+ /*
+ * If this constraint hasn't been fully validated yet, we must
+ * ignore it here.
+ */
+ if (!attachRel_constr->check[i].ccvalid)
+ continue;
+
+ cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+ /*
+ * Run each expression through const-simplification and
+ * canonicalization. It is necessary, because we will be
+ * comparing it to similarly-processed qual clauses, and may fail
+ * to detect valid matches without this.
+ */
+ cexpr = eval_const_expressions(NULL, cexpr);
+ cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+ existConstraint = list_concat(existConstraint,
+ make_ands_implicit((Expr *) cexpr));
+ }
+
+ existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+ /* And away we go ... */
+ if (predicate_implied_by(partConstraint, existConstraint))
+ skip_validate = true;
+ }
+
+ if (skip_validate)
+ elog(NOTICE, "skipping scan to validate partition constraint");
+
+ /*
+ * Set up to have the table to be scanned to validate the partition
+ * constraint (see partConstraint above). If it's a partitioned table,
+ * we instead schdule its leaf partitions to be scanned instead.
+ */
+ if (!skip_validate)
+ {
+ List *all_parts;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessExclusiveLock, NULL);
+ else
+ all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ foreach(lc, all_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid part_relid = lfirst_oid(lc);
+ Relation part_rel;
+ Expr *constraint;
+
+ /* Lock already taken */
+ if (part_relid != RelationGetRelid(attachRel))
+ part_rel = heap_open(part_relid, NoLock);
+ else
+ part_rel = attachRel;
+
+ /*
+ * Skip if it's a partitioned table. Only RELKIND_RELATION
+ * relations (ie, leaf partitions) need to be scanned.
+ */
+ if (part_rel != attachRel &&
+ part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(part_rel, NoLock);
+ continue;
+ }
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, part_rel);
+
+ constraint = linitial(partConstraint);
+ tab->partition_quals = make_ands_implicit((Expr *) constraint);
+
+ /* keep our lock until commit */
+ if (part_rel != attachRel)
+ heap_close(part_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that the new partition is now included
+ * in rel's partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
/*
* Finally create the relation. This also creates the type.
*/
- DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+ DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+ NULL);
return address;
}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
- address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+ address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+ NULL);
Assert(address.objectId != InvalidOid);
return address;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+ PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(listdatums);
+ COPY_NODE_FIELD(lowerdatums);
+ COPY_NODE_FIELD(upperdatums);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+ PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+ COPY_SCALAR_FIELD(infinite);
+ COPY_NODE_FIELD(value);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundSpec:
+ retval = _copyPartitionBoundSpec(from);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _copyPartitionRangeDatum(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 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(listdatums);
+ COMPARE_NODE_FIELD(lowerdatums);
+ COMPARE_NODE_FIELD(upperdatums);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+ COMPARE_SCALAR_FIELD(infinite);
+ COMPARE_NODE_FIELD(value);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundSpec:
+ retval = _equalPartitionBoundSpec(a, b);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _equalPartitionRangeDatum(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
+ case T_PartitionBoundSpec:
+ loc = ((const PartitionBoundSpec *) expr)->location;
+ break;
+ case T_PartitionRangeDatum:
+ loc = ((const PartitionRangeDatum *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUND");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(listdatums);
+ WRITE_NODE_FIELD(lowerdatums);
+ WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+ WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+ WRITE_BOOL_FIELD(infinite);
+ WRITE_NODE_FIELD(value);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundSpec:
+ _outPartitionBoundSpec(str, obj);
+ break;
+ case T_PartitionRangeDatum:
+ _outPartitionRangeDatum(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+ READ_LOCALS(PartitionBoundSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_NODE_FIELD(listdatums);
+ READ_NODE_FIELD(lowerdatums);
+ READ_NODE_FIELD(upperdatums);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+ READ_LOCALS(PartitionRangeDatum);
+
+ READ_BOOL_FIELD(infinite);
+ READ_NODE_FIELD(value);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONBOUND", 14))
+ return_value = _readPartitionBoundSpec();
+ else if (MATCH("PARTRANGEDATUM", 14))
+ return_value = _readPartitionRangeDatum();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2387df9..b458e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionSpec *partspec;
+ PartitionRangeDatum *partrange_datum;
}
%type <node> stmt schema_stmt
@@ -551,6 +552,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partbound_datum
+%type <list> partbound_datum_list
+%type <partrange_datum> PartitionRangeDatum
+%type <list> range_datum_list
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -576,7 +584,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
@@ -592,7 +600,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
@@ -2378,6 +2387,31 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ 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;
+ }
;
alter_column_default:
@@ -2473,6 +2507,73 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partbound_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatums = $5;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ n->lowerdatums = $5;
+ n->upperdatums = $9;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+partbound_datum:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partbound_datum_list:
+ partbound_datum { $$ = list_make1($1); }
+ | partbound_datum_list ',' partbound_datum
+ { $$ = lappend($1, $3); }
+ ;
+
+range_datum_list:
+ PartitionRangeDatum { $$ = list_make1($1); }
+ | range_datum_list ',' PartitionRangeDatum
+ { $$ = lappend($1, $3); }
+ ;
+
+PartitionRangeDatum:
+ UNBOUNDED
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = true;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | partbound_datum
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = false;
+ n->value = $1;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
/*****************************************************************************
*
@@ -2890,6 +2991,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2935,6 +3074,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2957,6 +3101,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2968,6 +3123,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4715,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;
+ }
;
/*****************************************************************************
@@ -13804,6 +14006,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13850,6 +14053,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4175ef5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ 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 transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
/*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ 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;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3045,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of rel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* tranform the values */
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+ cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+ *result_spec;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+
+ result_spec = copyObject(spec);
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListCell *cell;
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ result_spec->listdatums = NIL;
+ foreach(cell, spec->listdatums)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+ ListCell *cell2;
+ bool duplicate;
+
+ value = (Node *) make_const(pstate, &con->val, con->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(pstate,
+ exprLocation((Node *) con))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ /* Don't add to the result if the value is a duplicate */
+ duplicate = false;
+ foreach(cell2, result_spec->listdatums)
+ {
+ Const *value2 = (Const *) lfirst(cell2);
+
+ if (equal(value, value2))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+
+ result_spec->listdatums = lappend(result_spec->listdatums,
+ value);
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ ListCell *cell1,
+ *cell2;
+ int i,
+ j;
+ char *colname;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+ if (list_length(spec->lowerdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("FROM must specify exactly one value per partitioning column")));
+ if (list_length(spec->upperdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("TO must specify exactly one value per partitioning column")));
+
+ i = j = 0;
+ result_spec->lowerdatums = result_spec->upperdatums = NIL;
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum,
+ *rdatum;
+ Node *value;
+ A_Const *lcon = NULL,
+ *rcon = NULL;
+
+ ldatum = (PartitionRangeDatum *) lfirst(cell1);
+ rdatum = (PartitionRangeDatum *) lfirst(cell2);
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ if (!ldatum->infinite)
+ lcon = (A_Const *) ldatum->value;
+ if (!rdatum->infinite)
+ rcon = (A_Const *) rdatum->value;
+
+ if (lcon)
+ {
+ value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ ldatum->value = value;
+ }
+
+ if (rcon)
+ {
+ value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ rdatum->value = value;
+ }
+
+ result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+ copyObject(ldatum));
+ result_spec->upperdatums = lappend(result_spec->upperdatums,
+ copyObject(rdatum));
+
+ ++i;
+ }
+
+ break;
+ }
+ }
+
+ return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_RELATION,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_FOREIGN_TABLE,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
CreateForeignTable((CreateForeignTableStmt *) stmt,
address.objectId);
EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2d16ea..eb16f70 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? If the partitioning structure did not change, that is,
+ * no partitions were added or removed to the relation, the oids array
+ * should still match element-by-element.
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The logic to iterate over
+ * the collections is local to partition.c.
+ */
+ if (pdesc1->boundinfo != NULL)
+ {
+ if (pdesc2->boundinfo == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key,
+ pdesc1->boundinfo, pdesc2->boundinfo))
+ return false;
+ }
+ else if (pdesc2->boundinfo != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2608,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 */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
RelationBuildPartitionKey(relation);
Assert(relation->rd_partkey != NULL);
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
restart = true;
}
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
Oid *partopclass,
Oid *partcollation);
extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
#endif /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..e1c01d2
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds. It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ *
+ * Note: Order of elements in the oids array is arbitrary when the table
+ * is list partitioned. Whereas in case of a range partitioned table, they
+ * are ordered to match the ascending order of partition ranges.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection boundinfo; /* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/* relcache support functions for partition descriptor */
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2);
+
+/* For commands/tablecmds.c's perusal */
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress);
+ ObjectAddress *typaddress, const char *queryString);
extern void RemoveRelations(DropStmt *drop);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundSpec,
+ T_PartitionRangeDatum,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff5ea81..4d8e3d2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+ NodeTag type;
+
+ char strategy;
+
+ /* List partition values */
+ List *listdatums;
+
+ /*
+ * Range partition lower and upper bounds; each member of the lists
+ * is a PartitionRangeDatum (see below).
+ */
+ List *lowerdatums;
+ List *upperdatums;
+
+ int location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+ NodeTag type;
+
+ bool infinite;
+ Node *value;
+
+ int location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1570,7 +1615,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
@@ -1796,7 +1843,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 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)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+ Node *bound);
#endif /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 981d19e..45d5a0d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -601,6 +604,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fb492ad..7201b12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,243 @@ ERROR: cannot inherit from partitioned table "partitioned"
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, foo;
+-- ATTACH PARTITION
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 FROM (1) TO (10);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+ ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR: cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR: cannot attach regular inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table child
+-- check that 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 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 that the table being attached has only columns present 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: New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child 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: child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing constraint "check_a"
+-- check that the constraint 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: child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+ERROR: partition constraint is violated by some row
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+ERROR: partition constraint is violated by some row
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- if we add a valid constraint to the table that only allows rows satisfying
+-- the desired partition constraint, the scan to check the rows will be
+-- skipped based on that constraint
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ALTER TABLE part_3 ADD CONSTRAINT partition_check_a check (a IN (3));
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+NOTICE: skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (1);
+ERROR: "part_3" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ERROR: circular inheritance not allowed
+DETAIL: "part_3" is already a child of "list_parted".
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+ERROR: circular inheritance not allowed
+DETAIL: "list_parted" is already a child of "list_parted".
+-- DETACH PARTITION
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check that the partition being detached is actually 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, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal
+-------------+------------
+ 0 | t
+ 0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal
+-------------+------------
+ 0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR: column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR: column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+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 inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR: constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR: constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR: constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR: constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+NOTICE: skipping scan to validate partition constraint
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0f15c98..01124e1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
Partition key: LIST ((a + 1))
DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR: specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR: TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR: cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR: cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR: cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR: partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR: partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR: partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 14 other objects
+DETAIL: drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..3f3855b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,203 @@ ALTER TABLE foo INHERIT partitioned;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, foo;
+
+-- ATTACH PARTITION
+
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that 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 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 that the table being attached has only columns present 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 that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+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 that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- if we add a valid constraint to the table that only allows rows satisfying
+-- the desired partition constraint, the scan to check the rows will be
+-- skipped based on that constraint
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ALTER TABLE part_3 ADD CONSTRAINT partition_check_a check (a IN (3));
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (1);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f100498..683b852 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
\d partitioned2
DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions 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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-12.patch (22.1K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-12.patch)
download | inline diff:
From 71280978356994aa2b9113948fcb770d8b8447a7 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 | 82 +++++++++++++++++++
src/bin/pg_dump/common.c | 86 ++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 118 ++++++++++++++++++++++++++--
src/bin/pg_dump/pg_dump.h | 12 +++
src/bin/psql/describe.c | 85 +++++++++++++++++---
src/test/regress/expected/create_table.out | 40 ++++++++++
src/test/regress/sql/create_table.sql | 12 +++
7 files changed, 415 insertions(+), 20 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundSpec:
+ {
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ switch (spec->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->listdatums != NIL);
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->lowerdatums != NIL &&
+ spec->upperdatums != NIL &&
+ list_length(spec->lowerdatums) ==
+ list_length(spec->upperdatums));
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " FROM");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->lowerdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ appendStringInfoString(buf, " TO");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->upperdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ break;
+
+ default:
+ elog(ERROR, "unrecognized partition strategy: %d",
+ (int) spec->strategy);
+ break;
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 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);
@@ -323,6 +338,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
*
@@ -924,6 +976,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 c443735..9deb035 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5595,9 +5595,16 @@ getInherits(Archive *fout, int *numInherits)
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
- /* find all the inheritance information */
-
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ /*
+ * Find all the inheritance information, excluding implicit inheritance
+ * via partitioning. We handle that case using getPartitions(), because
+ * we want more information about partitions than just the parent-child
+ * relationship.
+ */
+ 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);
@@ -5624,6 +5631,70 @@ 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;
+
+ /* Before version 10, there are no partitions */
+ if (fout->remoteVersion < 100000)
+ {
+ *numPartitions = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find the inheritance and boundary information about partitions */
+
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -14155,6 +14226,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 */
@@ -14183,8 +14265,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;
@@ -14213,7 +14298,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");
}
@@ -14271,15 +14357,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 (");
@@ -14449,6 +14542,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 527d6ad..3f3c777 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;
@@ -625,6 +636,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 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2535,8 +2563,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)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ 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 level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+ Table "public.part_b"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+ "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+ Table "public.part_c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
-- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
-- create a level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Teach-a-few-places-to-use-partition-check-quals-12.patch (30.6K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/6-0005-Teach-a-few-places-to-use-partition-check-quals-12.patch)
download | inline diff:
From 5da702d6ee1ec976243ca9d64671e242ad8993d9 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 quals.
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 | 75 +++++++++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/optimizer/util/plancat.c | 20 +++
src/include/nodes/execnodes.h | 4 +
src/test/regress/expected/inherit.out | 271 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 76 +++++++++
src/test/regress/expected/update.out | 27 +++
src/test/regress/sql/inherit.sql | 48 ++++++
src/test/regress/sql/insert.sql | 56 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 598 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0ef590a..77d4dcb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2503,7 +2503,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 9773272..ea3f59a 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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,28 @@ 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 for relation \"%s\" violates partition constraint",
+ 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 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,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)
@@ -922,7 +922,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 ad07baa..a2cbf14 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"
@@ -1139,6 +1140,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.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition predicates, if any */
+ pcqual = RelationGetPartitionQual(relation, true);
+ 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 f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_cd
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_null
+ Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..5d58d23 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+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 for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+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 from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (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 for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL: Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR: new row for relation "part_ee_ff_1_10" violates partition constraint
+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 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..bdb4e2c 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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 for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..5aedeef 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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 from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (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..5392fa5 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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
[text/x-diff] 0006-Introduce-a-PartitionTreeNode-data-structure-12.patch (8.0K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/7-0006-Introduce-a-PartitionTreeNode-data-structure-12.patch)
download | inline diff:
From 222ba49b7944ef69ce8c8d2d0b2330929fec917e 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 | 208 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 213 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 97cb9a4..ae9e945 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
int index;
} PartitionListValue;
+/*
+ * 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_parts 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;
+
static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
static List *generate_partition_qual(Relation rel, bool recurse);
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -878,6 +936,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * 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->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1318,6 +1423,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 e1c01d2..ab6d3a8 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
/* relcache support functions for partition descriptor */
extern void RelationBuildPartitionDesc(Relation relation);
@@ -51,4 +52,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
extern Oid get_partition_parent(Oid relid);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0007-Tuple-routing-for-partitioned-tables-12.patch (43.7K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/8-0007-Tuple-routing-for-partitioned-tables-12.patch)
download | inline diff:
From dacc77a2301da6e40311d8b71ca2b7707402a0c9 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 | 327 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 205 +++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 +++++-
src/backend/executor/nodeModifyTable.c | 142 +++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 77 +++++++
src/backend/optimizer/util/plancat.c | 13 ++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 59 ++++++-
src/test/regress/sql/insert.sql | 28 +++
18 files changed, 926 insertions(+), 9 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index ae9e945..373e9e5 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -194,6 +206,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
static bool equal_range_info(PartitionKey key,
PartitionRangeInfo *r1, PartitionRangeInfo *r2);
static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg);
static bool partition_rbound_eq(PartitionKey key,
PartitionRangeBound *b1, PartitionRangeBound *b2);
typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1442,7 +1456,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;
@@ -1526,6 +1540,284 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ 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);
+
+ index = 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.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to perform recursion as the selected partition is partitioned
+ * itself. Locate the PartitionTreeNode corresponding to the partition
+ * 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)
+{
+ PartitionListInfo listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+
+ if (isnull && listinfo.has_null)
+ return listinfo.null_index;
+ else if (!isnull)
+ {
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ value);
+ if (found >= 0)
+ return listinfo.indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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)
+{
+ int offset;
+ PartitionRangeInfo rangeinfo;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ offset = partition_rbound_bsearch(key,
+ rangeinfo.bounds, rangeinfo.nbounds,
+ tuple, partition_rbound_datum_cmp,
+ true, NULL);
+
+ /*
+ * Offset returned is such that the bound at offset is found to be
+ * less or equal with the tuple. That is, the tuple belongs to the
+ * partition with the rangeinfo.bounds[offset] as the lower bound and
+ * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+ * is indeed marked !lower (that is, it's an upper bound). If it turns
+ * out that it is a lower bound then the corresponding index will be -1,
+ * which means no valid partition exists.
+ */
+ return rangeinfo.indexes[offset+1];
+}
+
/* List partition related support functions */
/*
@@ -1787,6 +2079,39 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
}
/*
+ * Return whether the passed in range bound <=, =, >= tuple specified in arg
+ *
+ * The 2nd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg)
+{
+ Datum *datums1 = bound->datums,
+ *datums2 = (Datum *) arg;
+ int i;
+ int32 cmpval;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ if (bound->infinite[i])
+ return bound->lower ? -1 : 1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ if (cmpval == 0 && !bound->inclusive)
+ return bound->lower ? 1 : -1;
+
+ return cmpval;
+}
+
+/*
* Are two (consecutive) range bounds equal without distinguishing lower
* and upper?
*/
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 77d4dcb..91e4d12 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.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ 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);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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
{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1705,6 +1801,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);
}
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2275,7 +2389,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_TABLE)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2383,6 +2498,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2410,6 +2526,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;
@@ -2431,10 +2548,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 */
@@ -2486,6 +2639,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 */
@@ -2506,7 +2684,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)
@@ -2536,7 +2723,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),
@@ -2556,6 +2744,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2569,7 +2763,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 395e116..685b184 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1294,6 +1294,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 ea3f59a..62bf8b7 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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);
@@ -2996,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 a612b08..d0a5306 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -272,6 +273,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;
/*
@@ -511,6 +537,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
@@ -1565,6 +1597,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)));
@@ -1655,6 +1688,98 @@ 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_TABLE)
+ {
+ 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)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(leaf_rel, CMD_INSERT);
+
+ 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)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ 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.
*/
@@ -1972,6 +2097,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 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_inherits_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ /*
+ * We are only interested in foreign tables. Note that this will
+ * also eliminate any partitioned tables since foreign tables can
+ * only ever be leaf partitions.
+ */
+ if (!oid_is_foreign_table(myoid))
+ continue;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 ab6d3a8..09727cf 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/rel.h"
@@ -56,4 +58,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
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 136276b..c62946f 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 ff8b66b..ce01008 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"
@@ -1147,6 +1148,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 5d58d23..619336b 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 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 5aedeef..210fab4 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
[text/x-diff] 0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-12.patch (24.7K, ../../c7972ce6-50b8-2a13-c1e5-658a32b549ca@lab.ntt.co.jp/9-0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-12.patch)
download | inline diff:
From 35bf77270874505e07b541d87e411e5a797f6178 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 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,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</>.
@@ -2793,12 +2793,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>
@@ -2842,59 +2845,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 >= 100 AND outletID < 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>
@@ -2911,8 +2877,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>
@@ -2940,7 +2908,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
@@ -2971,12 +2939,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 FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('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 FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
</programlisting>
Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3024,110 +2965,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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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">
@@ -3145,22 +2995,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
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
were created above:
<programlisting>
-CREATE TABLE measurement_y2008m02 (
- CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
</programlisting>
As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 FROM ('2017-07-01') TO ('2017-08-01');
</programlisting>
</para>
</sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
<programlisting>
SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= DATE '2017-01-01';
</programlisting>
Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate >= 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>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
</programlisting>
Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3397,9 +3173,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>
@@ -3423,18 +3199,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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-10 19:04 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-10 19:04 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Nov 10, 2016 at 7:40 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> I think "partitioning key" is a bit awkward and actually prefer
>> "partiton key". But "partition method" sounds funny so I would go
>> with "partitioning method".
>
> OK, "partition key" and "partitioning method" it is then. Source code
> comments, error messages, variables call the latter (partitioning)
> "strategy" though which hopefully is fine.
Oh, I like "partitioning strategy". Can we standardize on that?
>>> Related to range partitioning, should we finalize on inclusive START/FROM
>>> and exclusive END/TO preventing explicit specification of the inclusivity?
>>
>> I would be in favor of committing the initial patch set without that,
>> and then considering the possibility of adding it later. If we
>> include it in the initial patch set we are stuck with it.
>
> OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
> with each of the range bounds.
>
> I haven't changed any code (such as comparison functions) that manipulates
> instances of PartitionRangeBound which has a flag called inclusive. I
> didn't remove the flag, but is instead just set to (is_lower ? true :
> false) when initializing from the parse node. Perhaps, there is some scope
> for further simplifying that code, which you probably alluded to when you
> proposed that we do this.
Yes, you need to rip out all of the logic that supports it. Having
the logic to support it but not the syntax is bad because then that
code can't be properly tested.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-10 21:51 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-10 21:51 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Nov 9, 2016 at 9:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> With all patches applied, "make check" fails with a bunch of diffs
>> that look like this:
>>
>> Check constraints:
>> - "pt1chk2" CHECK (c2 <> ''::text)
>> "pt1chk3" CHECK (c2 <> ''::text)
>
> Hm, I can't seem to reproduce this one. Is it perhaps possible that you
> applied the patches on top of some other WIP patches or something?
Nope. I just checked and this passes with only 0001 and 0002 applied,
but when I add 0003 and 0004 then it starts failing. It appears that
the problem starts at this point in the foreign_data test:
ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
After that command, in the expected output, pt1chk2 stops showing up
in the output of \d+ pt1, but continues to appear in the output of \d+
ft2. With your patch, however, it stops showing up for ft2 also. If
that's not also happening for you, it might be due to an uninitialized
variable someplace.
+ /* Force inheritance recursion, if partitioned table. */
Doesn't match code (any more).
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-11 10:30 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-11 10:30 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/11 6:51, Robert Haas wrote:
> On Wed, Nov 9, 2016 at 9:58 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> With all patches applied, "make check" fails with a bunch of diffs
>>> that look like this:
>>>
>>> Check constraints:
>>> - "pt1chk2" CHECK (c2 <> ''::text)
>>> "pt1chk3" CHECK (c2 <> ''::text)
>>
>> Hm, I can't seem to reproduce this one. Is it perhaps possible that you
>> applied the patches on top of some other WIP patches or something?
>
> Nope. I just checked and this passes with only 0001 and 0002 applied,
> but when I add 0003 and 0004 then it starts failing.
Sorry, it definitely wasn't an error on your part.
> It appears that
> the problem starts at this point in the foreign_data test:
>
> ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
>
> After that command, in the expected output, pt1chk2 stops showing up
> in the output of \d+ pt1, but continues to appear in the output of \d+
> ft2. With your patch, however, it stops showing up for ft2 also. If
> that's not also happening for you, it might be due to an uninitialized
> variable someplace.
Thanks for the context. I think I found the culprit variable in
MergeConstraintsIntoExisting() and fixed it. As you correctly guessed,
the uninitialized variable caused (in your environment) even non-partition
child relations to be treated partitions and hence forced any merged
constraints to be non-local in all cases, not just in case of partitions.
Which meant the command you quoted would even drop the ft2's (a child)
constraint because its conislocal is wrongly false.
>
> + /* Force inheritance recursion, if partitioned table. */
>
> Doesn't match code (any more).
Fixed.
>>> I think "partitioning key" is a bit awkward and actually prefer
>>> "partiton key". But "partition method" sounds funny so I would go
>>> with "partitioning method".
>>
>> OK, "partition key" and "partitioning method" it is then. Source code
>> comments, error messages, variables call the latter (partitioning)
>> "strategy" though which hopefully is fine.
>
> Oh, I like "partitioning strategy". Can we standardize on that?
OK, done.
>>> I would be in favor of committing the initial patch set without that,
>>> and then considering the possibility of adding it later. If we
>>> include it in the initial patch set we are stuck with it.
>>
>> OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
>> with each of the range bounds.
>>
>> I haven't changed any code (such as comparison functions) that manipulates
>> instances of PartitionRangeBound which has a flag called inclusive. I
>> didn't remove the flag, but is instead just set to (is_lower ? true :
>> false) when initializing from the parse node. Perhaps, there is some scope
>> for further simplifying that code, which you probably alluded to when you
>> proposed that we do this.
>
> Yes, you need to rip out all of the logic that supports it. Having
> the logic to support it but not the syntax is bad because then that
> code can't be properly tested.
Agreed, done.
Attached updated patches.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-13.patch (114.3K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-13.patch)
download | inline diff:
From 793b9487bfe22e5bcb299f326f1c18a93a0c4cc8 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.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 166 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 24 +-
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 | 450 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 ++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 10 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/utils/cache/relcache.c | 268 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 3 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 29 ++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 68 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 43 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 29 ++
src/test/regress/sql/create_table.sql | 143 +++++++++
51 files changed, 1948 insertions(+), 54 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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 part of the 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, 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>partcollation</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
+ the collation to use for partitioning.
+ </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>partattrs</>. 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..1a95219 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,41 @@ 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</literal> clause specifies a strategy
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partition key</firstterm>
+ for the table. When using range partitioning, the partition key can
+ include multiple columns or expressions, but for list partitioning, the
+ partition key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions),
+ which are created using separate <literal>CREATE TABLE</> commands.
+ The partitioned table is itself empty. A data row inserted into the
+ table is routed to a partition based on the value of columns or
+ expressions in the partition key. If no existing partition matches
+ the values in the new row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * StorePartitionKey
+ * Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition attribute numbers, opclass OIDs into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references, which will depend on the table itself; there is no
+ * separate partition key object.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ /*
+ * We must invalidate the relcache so that the next
+ * CommandCounterIncrement() will cause the same to be rebuilt using the
+ * information in just created catalog entry.
+ */
+ CacheInvalidateRelcache(rel);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Identify the opclass to use.
*/
- classOidP[attn] = GetIndexOpClass(attribute->opclass,
- atttype,
- accessMethodName,
- accessMethodId);
+ classOidP[attn] = ResolveOpClass(attribute->opclass,
+ atttype,
+ accessMethodName,
+ accessMethodId);
/*
* Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
*/
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..e5bcb89 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
rel = relation_open(relationId, AccessExclusiveLock);
/*
+ * Process the partitioning specification (if any) and store the
+ * partition key information into the catalog.
+ */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
+ /*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression. It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false. That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (attnum == partattno)
+ {
+ if (used_in_expr)
+ *used_in_expr = false;
+ return true;
+ }
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+
+ /* Find all attributes referenced */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ {
+ if (used_in_expr)
+ *used_in_expr = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5842,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 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in the partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference partitioned table \"%s\"",
+ RelationGetRelationName(pkrel))));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in the partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partitioning strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears more than once in partition key",
+ pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ /* Simple attribute reference */
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+
+ /* Note that whole-row references can't happen here; see below */
+ }
+ else
+ {
+ /* Expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ Bitmapset *expr_attrs = NULL;
+
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+
+ /* Cannot have expressions containing whole-row references */
+ pull_varattnos(expr, 1, &expr_attrs);
+ if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition key expressions cannot contain whole-row references")));
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = ResolveOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
case T_TriggerTransition:
retval = _equalTriggerTransition(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition:
_outTriggerTransition(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -545,6 +547,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partition key */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of columns in the
+ * partition key */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* list of expressions in the partitioning
+ * key; one item for each zero entry in
+ * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy; /* partitioning strategy ('list' or 'range') */
+ List *partParams; /* List of PartitionElems */
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partition key */
+ AttrNumber *partattrs; /* attribute numbers of columns in the
+ * partition key */
+ List *partexprs; /* list of expressions in the partitioning
+ * key, or NIL */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation per attribute */
+ Oid *partcollation;
+
+ /* Type information per attribute */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
c4 | integer | | |
DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR: cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR: cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR: cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+ERROR: column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+ERROR: partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table partitioned depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR: cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-13.patch (23.9K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-13.patch)
download | inline diff:
From 11b2b9278bbdb515d788c62c87143917c03d7937 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 | 159 ++++++++++++++++++++++++++++
src/bin/pg_dump/common.c | 4 +
src/bin/pg_dump/pg_dump.c | 66 +++++++++++-
src/bin/pg_dump/pg_dump.h | 2 +
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 | 20 ++++-
src/test/regress/sql/create_table.sql | 6 +-
10 files changed, 301 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 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_table.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,
@@ -1412,6 +1414,163 @@ 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 opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ oidvector *partcollation;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass and partcollation the hard way */
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partcollation, &isnull);
+ Assert(!isnull);
+ partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+ /*
+ * Get the 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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relid), relid);
+
+ initStringInfo(&buf);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+ Oid keycolcollation;
+ Oid partcoll;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple attribute reference */
+ char *attname;
+ int32 keycoltypmod;
+
+ attname = get_relid_attribute_name(relid, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ get_atttypetypmodcoll(relid, attnum,
+ &keycoltype, &keycoltypmod,
+ &keycolcollation);
+ }
+ else
+ {
+ /* 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, str);
+ keycoltype = exprType(partkey);
+ keycolcollation = exprCollation(partkey);
+ }
+
+ /* Add collation, if not default for column */
+ partcoll = partcollation->values[keyno];
+ if (OidIsValid(partcoll) && partcoll != keycolcollation)
+ appendStringInfo(&buf, " COLLATE %s",
+ generate_collation_name((partcoll)));
+
+ /* 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/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading policies\n");
getPolicies(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading partition key information for interesting tables\n");
+ getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4da297f..c443735 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1216,9 +1216,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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2074,6 +2075,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4946,7 +4950,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,
@@ -4960,7 +4964,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5488,7 +5493,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6897,6 +6904,47 @@ getTransforms(Archive *fout, int *numTransforms)
}
/*
+ * getTablePartitionKeyInfo -
+ * for each interesting partitioned table, read information about its
+ * partition key
+ *
+ * modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ int i,
+ ntups;
+ PGresult *res;
+
+ /* No partitioned tables before 10 */
+ if (fout->remoteVersion < 100000)
+ return;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &(tblinfo[i]);
+
+ /* Only partitioned tables have partition key */
+ if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ resetPQExpBuffer(q);
+ appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+ Assert(ntups == 1);
+ tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+ }
+}
+
+/*
* getTableAttrs -
* for each interesting table, read info about its attributes
* (names, types, default values, CHECK constraints, etc)
@@ -14249,6 +14297,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -14309,6 +14360,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -14327,7 +14379,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -14544,6 +14597,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 a60cf95..527d6ad 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.
@@ -648,5 +649,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
#endif /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1566,8 +1568,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)");
}
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,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')
{
headers[cols++] = gettext_noop("Collation");
headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,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");
}
@@ -1748,7 +1758,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);
@@ -1756,14 +1766,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 (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 */
@@ -1902,7 +1931,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;
@@ -2461,7 +2490,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;
@@ -2665,7 +2694,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
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3048,7 +3079,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 b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,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 */
@@ -483,7 +483,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 */
@@ -513,7 +513,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 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,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 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,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 e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
ERROR: cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+ Table "public.partitioned"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+ d | text | | |
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+ Table "public.partitioned2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST ((a + 1))
+
DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-13.patch (202.5K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-13.patch)
download | inline diff:
From 5eb5c7723697398819cfd3669fbfad58603ad84c 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.
pg_class gets new fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 103 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 26 +
doc/src/sgml/ref/create_table.sgml | 92 ++-
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 105 ++-
src/backend/catalog/partition.c | 1646 ++++++++++++++++++++++++++++
src/backend/commands/createas.c | 2 +-
src/backend/commands/sequence.c | 2 +-
src/backend/commands/tablecmds.c | 981 +++++++++++++++--
src/backend/commands/typecmds.c | 3 +-
src/backend/commands/view.c | 3 +-
src/backend/nodes/copyfuncs.c | 47 +
src/backend/nodes/equalfuncs.c | 41 +
src/backend/nodes/nodeFuncs.c | 6 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 34 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_utilcmd.c | 260 +++++-
src/backend/tcop/utility.c | 6 +-
src/backend/utils/cache/relcache.c | 93 ++-
src/include/catalog/heap.h | 1 +
src/include/catalog/partition.h | 48 +
src/include/catalog/pg_class.h | 22 +-
src/include/commands/tablecmds.h | 2 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 52 +-
src/include/parser/kwlist.h | 2 +
src/include/parser/parse_utilcmd.h | 2 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 240 ++++
src/test/regress/expected/create_table.out | 187 ++++
src/test/regress/sql/alter_table.sql | 200 ++++
src/test/regress/sql/create_table.sql | 153 +++
34 files changed, 4498 insertions(+), 139 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6139ab1..31352a2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..e27183c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
+ DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@@ -166,6 +168,11 @@ 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.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +711,51 @@ 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 table (which might itself be partitioned)
+ as a partition of the target table. The partition bound specification
+ must correspond to the partitioning strategy and partition key of the
+ target table. The table to be attached must have all the same columns
+ as the target table and no more; moreover, the column types must also
+ match. Also, it must have all the <literal>NOT NULL</literal> and
+ <literal>CHECK</literal> constraints present in the target table.
+ If some <literal>CHECK</literal> constraint of the table being attached
+ is marked <literal>NO INHERIT</literal>, the command will fail; such
+ constraints must be recreated without the <literal>NO INHERIT</literal>
+ clause. Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+ and <literal>FOREIGN KEY</literal> constraints are not considered.
+ </para>
+
+ <para>
+ A full table scan is performed on the table being attached to check that
+ no existing row in the table violates the partition constraint. It is
+ possible to avoid this potentially expensive scan by adding a valid
+ <literal>CHECK</literal> constraint to the table beforehand that only
+ allows rows satisfying the desired partition constraint. In this case,
+ it will be determined using such a constraint that existing rows in the
+ table satisfies the partition constraint, so the table scan to check the
+ same will be skipped. When adding a range partition, also add
+ <literal>NOT NULL</literal> constraint to each partition key column,
+ because <literal>NULL</literal> values are disallowed in the partition
+ key columns when using range partitioning.
+ </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, but no longer has any
+ ties to the table from which it was detached.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -721,8 +773,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>.
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.
+ To add the table as a new child of a parent table, or as a new partition
+ of a partitioned table, you must own the parent table as well.
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
@@ -938,6 +990,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 or to 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>
@@ -978,6 +1048,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</para>
<para>
+ Similarly, when attaching a new partition it is scanned to verify that
+ existing rows meet the partition constraint.
+ </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.
@@ -1047,6 +1122,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited.
+ A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+ partitioned table, because all partitions of a table must have the same
+ columns as the partitioning root.
</para>
<para>
@@ -1233,6 +1311,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 FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+ <para>
+ Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+ ATTACH PARTITION cities_west FOR VALUES IN ('los angeles', 'san fransisco');
+</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..5d0dcf5 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,12 @@ 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.
+ </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 +329,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</>, as a partition
+ of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+ PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('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 1a95219..3eb3b3c 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) [ INCLUSIVE | EXCLUSIVE ] }
+
<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>] [, ... ] ) ]
@@ -232,6 +247,50 @@ 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.
+ </para>
+
+ <para>
+ The partition bound specification must correspond to the partitioning
+ method and partition key of the parent table, and must not overlap with
+ any existing partition of that 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.
+ Defaults and constraints can optionally be specified for each of the
+ inherited columns. One can also specify table constraints in addition
+ to those inherited from the parent. If a check constraint with the name
+ matching one of the parent's constraint is specified, it is merged with
+ the latter, provided the specified condition is same.
+ </para>
+
+ <para>
+ Rows inserted into a partitioned table will be automatically routed to
+ the correct partition. If no suitable partition exists, an error will
+ occur.
+ </para>
+
+ <para>
+ A partition must have the same column names and types as the table of
+ which it is a partition. Therefore, modifications to the column names
+ or types of the partitioned table will automatically propagate to all
+ children, as will operations such as TRUNCATE which normally affect a
+ table and all of its inheritance children. It is also possible to
+ TRUNCATE a partition individually, just as for an inheritance child.
+ Dropping a partition directly using <literal>DROP TABLE</literal>
+ will fail; it must be <firstterm>detached</> from the parent first.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
CREATE TABLE cities (
name text not null,
population int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(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 FROM ('2016-07-01') TO ('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 Francisco');
+</programlisting></para>
+
+ <para>
+ Create partition of a list partitioned table that is itself further
+ partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+ PARTITION OF cities (
+ CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+ PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
</programlisting></para>
</refsect1>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 754a08b..64fc283 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"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ /* relpartbound is set by updating this tuple, if necessary */
+ nulls[Anum_pg_class_relpartbound - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ /* relispartition is always set by updating this tuple later */
+ new_rel_reltup->relispartition = false;
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
heap_drop_with_catalog(Oid relid)
{
Relation rel;
+ Oid parentOid;
+ Relation parent = NULL;
/*
* Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
rel = relation_open(relid, AccessExclusiveLock);
/*
+ * If the relation is a partition, we must grab exclusive lock on its
+ * parent because we need to update its partition descriptor. We must
+ * take a table lock strong enough to prevent all queries on the parent
+ * from proceeding until we commit and send out a shared-cache-inval
+ * notice that will make them update their partition descriptor.
+ * Sometimes, doing this is cycles spent uselessly, especially if the
+ * parent will be dropped as part of the same command anyway.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ parentOid = get_partition_parent(relid);
+ parent = heap_open(parentOid, AccessExclusiveLock);
+ }
+
+ /*
* There can no longer be anyone *else* touching the relation, but we
* might still have open queries or cursors, or pending trigger events, in
* our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
* delete relation tuple
*/
DeleteRelationTuple(relid);
+
+ if (parent)
+ {
+ CacheInvalidateRelcache(parent);
+ heap_close(parent, NoLock); /* keep the lock */
+ }
}
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
* definition) then interpret addition of a local constraint as a
* legal merge. This allows ALTER ADD CONSTRAINT on parent and
* child tables to be given in either order with same end state.
+ * However if the relation is a partition, all inherited
+ * constraints are always non-local, including those that were
+ * merged.
*/
- if (is_local && !con->conislocal)
+ if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
allow_merge = true;
if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
tup = heap_copytuple(tup);
con = (Form_pg_constraint) GETSTRUCT(tup);
- if (is_local)
- con->conislocal = true;
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ con->coninhcount = 1;
+ con->conislocal = false;
+ }
else
- con->coninhcount++;
+ {
+ if (is_local)
+ con->conislocal = true;
+ else
+ con->coninhcount++;
+ }
+
if (is_no_inherit)
{
Assert(is_local);
@@ -3176,3 +3224,52 @@ RemovePartitionKeyByRelId(Oid relid)
ReleaseSysCache(tuple);
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * StorePartitionBound
+ * Update pg_class tuple of rel to store the partition bound and set
+ * relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+ Relation classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+ {
+ Form_pg_class classForm;
+ bool isnull;
+
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(!classForm->relispartition);
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+ }
+#endif
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..7810c9d
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1646 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; has
+ * same length as the values array above */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ int index;
+ Datum *datums; /* range bound datums */
+ bool *infinite; /* infinite flag for each datum */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, length of the bounds array is far less than 2 * nparts,
+ * because a partition's upper bound and the next partition's lower bound
+ * are same in common cases, and we only store one of them.
+ *
+ * There are nbound members in bounds and nbounds+1 in indexes.
+ */
+typedef struct PartitionRangeInfo
+{
+ PartitionRangeBound **bounds;
+ int nbounds;
+ int *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation
+ */
+typedef struct BoundCollectionData
+{
+ char strategy; /* list or range bounds? */
+
+ union
+ {
+ struct PartitionListInfo lists;
+ struct PartitionRangeInfo ranges;
+ } bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+ Datum value;
+ int index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+ PartitionRangeBound *,
+ void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *inhoids,
+ *partoids;
+ Oid *oids = NULL;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ PartitionListValue **all_values = NULL;
+ int all_values_count = 0;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ PartitionRangeBound **distinct_bounds = NULL;
+ int num_distinct_bounds = 0;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ partoids = NIL;
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+
+ /*
+ * It is possible that the pg_class tuple of a partition has not been
+ * updated yet to set its relpartbound field. The only case where
+ * this happens is when we open the parent relation to check using its
+ * partition descriptor that a new partition's bound does not overlap
+ * some existing partition.
+ */
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+ {
+ ReleaseSysCache(tuple);
+ continue;
+ }
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ partoids = lappend_oid(partoids, inhrelid);
+ }
+
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc(nparts * sizeof(Oid));
+ i = 0;
+ foreach (cell, partoids)
+ oids[i++] = lfirst_oid(cell);
+
+ /* Convert from node to the internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ List *non_null_values = NIL;
+
+ /*
+ * Create a unified list of non-null values across all
+ * partitions.
+ */
+ i = 0;
+ found_null_partition = false;
+ null_partition_index = -1;
+ foreach(cell, boundspecs)
+ {
+ ListCell *c;
+ PartitionBoundSpec *spec = lfirst(cell);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ elog(ERROR, "invalid strategy in partition bound spec");
+
+ foreach (c, spec->listdatums)
+ {
+ Const *val = lfirst(c);
+ PartitionListValue *list_value = NULL;
+
+ if (!val->constisnull)
+ {
+ list_value = (PartitionListValue *)
+ palloc0(sizeof(PartitionListValue));
+ list_value->index = i;
+ list_value->value = val->constvalue;
+ }
+ else
+ {
+ /*
+ * Never put a null into the values array, flag
+ * instead for the code further down below where
+ * we construct the actual relcache struct.
+ */
+ if (found_null_partition)
+ elog(ERROR, "found null more than once");
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+
+ if (list_value)
+ non_null_values = lappend(non_null_values,
+ list_value);
+ }
+
+ i++;
+ }
+
+ all_values_count = list_length(non_null_values);
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (PartitionListValue **)
+ palloc(all_values_count *
+ sizeof(PartitionListValue *));
+ i = 0;
+ foreach(cell, non_null_values)
+ {
+ PartitionListValue *src = lfirst(cell);
+
+ all_values[i] = (PartitionListValue *)
+ palloc(sizeof(PartitionListValue));
+ all_values[i]->value = src->value;
+ all_values[i]->index = src->index;
+ i++;
+ }
+
+ qsort_arg(all_values, all_values_count,
+ sizeof(PartitionListValue *),
+ qsort_partition_list_value_cmp,
+ (void *) key);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ int j, k;
+ PartitionRangeBound **all_bounds,
+ *prev;
+ bool *distinct_indexes;
+
+ all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+ sizeof(PartitionRangeBound *));
+ distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
+
+ /*
+ * Create a unified list of range bounds across all the
+ * partitions.
+ */
+ i = j = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundSpec *spec = lfirst(cell);
+ PartitionRangeBound *lower, *upper;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ elog(ERROR, "invalid strategy in partition bound spec");
+
+ lower = make_one_range_bound(key, i, spec->lowerdatums,
+ true);
+ upper = make_one_range_bound(key, i, spec->upperdatums,
+ false);
+ all_bounds[j] = lower;
+ all_bounds[j+1] = upper;
+ j += 2;
+ i++;
+ }
+ Assert(j == 2 * nparts);
+
+ /* Sort all the bounds in ascending order */
+ qsort_arg(all_bounds, 2 * nparts,
+ sizeof(PartitionRangeBound *),
+ qsort_partition_rbound_cmp,
+ (void *) key);
+ /*
+ * Count the number of distinct bounds to allocate an array
+ * of that size.
+ */
+ num_distinct_bounds = 0;
+ prev = NULL;
+ for (i = 0; i < 2 * nparts; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ /*
+ * Count the current bound if it is distinct from the
+ * previous one. Also, store if the index i contains
+ * a distinct bound that we'd like put in the relcache
+ * array.
+ */
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ {
+ distinct_indexes[i] = true;
+ num_distinct_bounds++;
+ }
+ else
+ distinct_indexes[i] = false;
+
+ prev = cur;
+ }
+
+ /*
+ * Finally save them in an array from where they will be
+ * copied into the relcache.
+ */
+ distinct_bounds = (PartitionRangeBound **)
+ palloc0(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ k = 0;
+ for (i = 0; i < 2 * nparts; i++)
+ {
+ if (distinct_indexes[i])
+ distinct_bounds[k++] = all_bounds[i];
+ }
+ Assert(k == num_distinct_bounds);
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ int *mapping;
+ int next_index = 0;
+
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->boundinfo = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ result->boundinfo->strategy = key->strategy;
+
+ /* Initialize mapping array with invalid values */
+ mapping = (int *) palloc(sizeof(int) * nparts);
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionListInfo listinfo;
+
+ listinfo.nvalues = all_values_count;
+ listinfo.has_null = found_null_partition;
+ listinfo.values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo.indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values. Indexes of individual values are mapped to
+ * canonical values so that they match for any two list
+ * partitioned tables with same number of partitions and same
+ * lists per partition. One way to canonicalize is to assign
+ * the index in all_values[] of the smallest value of each
+ * partition as the index of all of the partition's values.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo.values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+
+ /* If the old index is has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo.indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * If null-accepting partition has no mapped index yet, assign
+ * one. This could happen if such partition accepts only null
+ * and hence not covered in the above loop which only handled
+ * non-null values.
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo.null_index = mapping[null_partition_index];
+ else
+ listinfo.null_index = -1;
+
+ result->boundinfo->bounds.lists = listinfo;
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeInfo rangeinfo;
+
+ rangeinfo.bounds = (PartitionRangeBound **)
+ palloc(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ rangeinfo.indexes = (int *)
+ palloc((num_distinct_bounds + 1) *
+ sizeof(int));
+
+ for (i = 0; i < num_distinct_bounds; i++)
+ {
+ rangeinfo.bounds[i] =
+ copy_range_bound(key, distinct_bounds[i]);
+
+ /*
+ * There is no mapping for invalid indexes.
+ *
+ * Any lower bounds in the distinct_bounds array have
+ * invalid indexes assigned, because the values between
+ * the previous bound (if there is one) and this (lower)
+ * bound are not part of the range of any existing
+ * partition.
+ */
+ if (rangeinfo.bounds[i]->lower)
+ rangeinfo.indexes[i] = -1;
+ else
+ {
+ int orig_index = rangeinfo.bounds[i]->index;
+
+ /* If the old index is has no mapping, assign one */
+ if (mapping[orig_index] == -1)
+ mapping[orig_index] = next_index++;
+
+ rangeinfo.indexes[i] = mapping[orig_index];
+ }
+ }
+ rangeinfo.indexes[i] = -1;
+ rangeinfo.nbounds = num_distinct_bounds;
+
+ result->boundinfo->bounds.ranges = rangeinfo;
+ break;
+ }
+ }
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of the
+ * list values or the range bounds.
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+ pfree(mapping);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2)
+{
+ if (b1->strategy != b2->strategy)
+ return false;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key,
+ &b1->bounds.lists, &b2->bounds.lists))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key,
+ &b1->bounds.ranges, &b2->bounds.ranges))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent. Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+ bool overlap = false;
+
+ parent = heap_open(parentId, NoLock); /* already locked */
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ if (pdesc->nparts > 0)
+ {
+ ListCell *cell;
+ bool new_has_null = false;
+ PartitionListInfo listinfo;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+ Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+ /* If the new partition's list contains null, set a flag. */
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ {
+ new_has_null = true;
+ break;
+ }
+ }
+
+ /*
+ * Now check against listinfo whether the new partition's
+ * values are not already taken by existing partitions.
+ */
+ if (new_has_null && listinfo.has_null)
+ {
+ with = listinfo.null_index;
+ overlap = true;
+ }
+ else
+ {
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+ int found = -1;
+
+ /* bsearch a new list's value in listinfo->values */
+ if (!val->constisnull)
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ val->constvalue);
+ if (found >= 0)
+ {
+ overlap = true;
+ with = listinfo.indexes[found];
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeBound *lower,
+ *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+ upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+ /*
+ * First check if the resulting range would be empty with
+ * specified bounds
+ */
+ if (partition_rbound_cmp(key, lower, upper) >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, spec->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ PartitionRangeInfo rangeinfo;
+ int idx1, idx2;
+ bool equal = false;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ /*
+ * Find the greatest index of a range bound that is less
+ * than the new lower bound.
+ */
+ idx1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, lower,
+ partition_rbound_cmp, false,
+ &equal);
+
+ /*
+ * If equal has been set to true, that means the new bound is
+ * found to be equal to some existing bound. offset1 == -1
+ * means that all existing range bounds are greater than the
+ * new bound. If offset >= 0, and there exists a gap between
+ * the corresponding bound and the next, new partition can
+ * still manage to fit within the same. To confirm whether it
+ * is so, check the new upper bound.
+ */
+ if (!equal && (idx1 < 0 || rangeinfo.indexes[idx1+1] < 0))
+ {
+ idx2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, upper,
+ partition_rbound_cmp,
+ false, &equal);
+
+ if (equal || idx1 != idx2)
+ {
+ overlap = true;
+ with = rangeinfo.indexes[idx2+1];
+ }
+ }
+ else
+ {
+ overlap = true;
+ if (idx1 == -1)
+ {
+ Assert(equal);
+ idx1 = 0;
+ }
+ with = rangeinfo.indexes[idx1+1];
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (overlap)
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, spec->location)));
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual = NIL;
+ TupleDesc parent_tupdesc = RelationGetDescr(parent);
+ AttrNumber parent_attno;
+ AttrNumber *partition_attnos;
+ bool found_whole_row;
+
+ Assert(key != NULL);
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, spec);
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, spec);
+ break;
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partition_attnos = (AttrNumber *)
+ palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+ for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+ parent_attno++)
+ {
+ Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+ char *attname = NameStr(attribute->attname);
+ AttrNumber partition_attno;
+
+ if (attribute->attisdropped)
+ continue;
+
+ partition_attno = get_attnum(RelationGetRelid(rel), attname);
+ partition_attnos[parent_attno - 1] = partition_attno;
+ }
+
+ my_qual = (List *) map_variable_attnos((Node *) my_qual,
+ 1, 0,
+ partition_attnos,
+ parent_tupdesc->natts,
+ &found_whole_row);
+ /* there can never be a whole-row reference in there*/
+ Assert(!found_whole_row);
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result;
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If the list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, remove the null Const from the list and create a IS NULL
+ * test and create an OR expression along with ScalarArrayOpExpr. We do
+ * it this way because the null-valued expression does not have the
+ * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(spec->listdatums); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ spec->listdatums = list_delete_cell(spec->listdatums,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = spec->listdatums;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ if (nulltest1)
+ result = list_make2(nulltest1, opexpr);
+ else if (nulltest2)
+ {
+ Expr *or;
+
+ or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+ result = list_make1(or);
+ }
+ else
+ result = list_make1(opexpr);
+
+ return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+
+ /*
+ * Iterate over columns of the key, emitting an OpExpr for each using
+ * the corresponding lower and upper datums as constant operands.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum = lfirst(cell1),
+ *udatum = lfirst(cell2);
+ Node *key_col;
+ Const *lower_val = NULL,
+ *upper_val = NULL;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+ Oid operoid;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Stop at this column if either of lower or upper datum is infinite,
+ * but do emit an OpExpr for the non-infinite datum.
+ */
+ if (!ldatum->infinite)
+ lower_val = (Const *) ldatum->value;
+ if (!udatum->infinite)
+ upper_val = (Const *) udatum->value;
+
+ /*
+ * If lower_val and upper_val are both finite and happen to be equal,
+ * emit only (key_col = lower_val) for this column, because all rows
+ * in this partition could only ever contain this value (ie, lower_val)
+ * in the current partitioning column. We must consider further
+ * columns because the above condition does not fully constrain the
+ * rows of this partition.
+ */
+ if (lower_val && upper_val)
+ {
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ /* Create the test expression */
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /* This can never be, but it's better to make sure */
+ if (i == key->partnatts - 1)
+ elog(ERROR, "invalid range bound specification");
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go over to consider the next column. */
+ i++;
+ continue;
+ }
+ }
+
+ /*
+ * We can say here that lower_val <> upper_val. Emit expressions
+ * (key_col >= lower_val) and (key_col < upper_val), then stop.
+ */
+ if (lower_val)
+ {
+ operoid = get_partition_operator(key, i,
+ BTGreaterEqualStrategyNumber,
+ &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ if (upper_val)
+ {
+ operoid = get_partition_operator(key, i,
+ BTLessStrategyNumber,
+ &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ /*
+ * We can stop at this column, because we would not have checked
+ * the next column when routing a given row into this partition.
+ */
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ /*
+ * First check if there exists an operator of the given strategy, with
+ * this column's type as both its lefttype and righttype, in the
+ * partitioning operator family specified for the column.
+ */
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+
+ /*
+ * If one doesn't exist, we must resort to using an operator in the same
+ * opreator family but with the operator class declared input type. It is
+ * OK to do so, because the column's type is known to be binary-coercible
+ * with the operator class input type (otherwise, the operator class in
+ * question would not have been accepted as the partitioning operator
+ * class). We must however inform the caller to wrap the non-Const
+ * expression with a RelabelType node to denote the implicit coercion. It
+ * ensures that the resulting expression structurally matches similarly
+ * processed expressions within the optimizer.
+ */
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+ else
+ *need_relabel = false;
+
+ if (!OidIsValid(operoid))
+ elog(ERROR, "could not find operator for partitioning");
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Get pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+ ReleaseSysCache(tuple);
+
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Keep the parent locked until commit */
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Check that two list partition bound collections are logically equal
+ *
+ * n is the number of partitions that either of l1 and l2 represents.
+ */
+static bool
+equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ if(l1->indexes[i] != l2->indexes[i])
+ return false;
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+ Datum value1 = (*(const PartitionListValue **) a)->value,
+ value2 = (*(const PartitionListValue **) b)->value;
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = partition_list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+ int i;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->index = index;
+ bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+ bound->lower = lower;
+
+ i = 0;
+ foreach (cell, datums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+
+ bound->infinite[i] = datum->infinite;
+
+ if (!bound->infinite[i])
+ {
+ Const *val = (Const *) datum->value;
+
+ if (val->constisnull)
+ elog(ERROR, "invalid range bound datum");
+ bound->datums[i] = val->constvalue;
+ }
+
+ i++;
+ }
+
+ return bound;
+}
+
+/*
+ * Make and return a copy of input PartitionRangeBound.
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->index = src->index;
+ result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+ result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+ result->lower = src->lower;
+
+ for (i = 0; i < partnatts; i++)
+ {
+ result->infinite[i] = src->infinite[i];
+ if (!result->infinite[i])
+ result->datums[i] = datumCopy(src->datums[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Compare ranges of two partitioned relations.
+ *
+ * Both r1 and r2 are known to contain n ranges (corresponding to n
+ * partitions).
+ */
+static bool
+equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+ int i;
+
+ if (r1->nbounds != r2->nbounds)
+ return false;
+
+ for (i = 0; i < r1->nbounds; i++)
+ {
+ if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+ return false;
+
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+ }
+
+ /* There is one more index than bounds */
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+
+ return true;
+}
+
+/*
+ * Return for two range bounds whether the 1st one is <=, =, >= the 2nd
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+ PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int32 cmpval;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ /*
+ * First, handle cases involving infinity, which don't require
+ * invoking the comparison proc.
+ */
+ if (b1->infinite[i] && b2->infinite[i])
+ {
+ /*
+ * 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[i])
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite[i])
+ return b2->lower ? 1 : -1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /*
+ * 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. Remember lower bounds are inclusive.
+ */
+ if (cmpval == 0)
+ {
+ /*
+ * If both are either inclusive or exclusive, they are trivially
+ * equal
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ /* Exclusive one is smaller of the two */
+ else
+ return b1->lower ? 1 : -1;
+ }
+
+ return cmpval;
+}
+
+/*
+ * Return whether two range bounds are equal simply by comparing datums
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ int32 cmpval = 0;
+
+ /*
+ * If either of them has infinite element, we can't equate them.
+ * Even if both were infinite, they'd have opposite signs (it always
+ * holds that b1 and b2 are lower and upper bound or vice versa).
+ */
+ if (b1->infinite[i] || b2->infinite[i])
+ return false;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+ PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple. The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal)
+{
+ int lo, hi, mid;
+
+ lo = -1;
+ hi = n - 1;
+ while (lo < hi)
+ {
+ PartitionRangeBound *cur;
+ int32 cmpval;
+
+ mid = (lo + hi + 1) / 2;
+ cur = bounds[mid];
+
+ cmpval = cmp(key, cur, probe);
+ if (is_equal)
+ *is_equal = (cmpval == 0);
+
+ if (cmpval < 0 || (equal_allowed && cmpval == 0))
+ lo = mid;
+ else
+ hi = mid - 1;
+ }
+
+ return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
* Create the relation. (This will error out if there's an existing view,
* so we don't need more code to complain if "replace" is false.)
*/
- intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+ intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
/*
* If necessary, create a TOAST table for the target table. Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists;
- address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+ address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
seqoid = address.objectId;
Assert(seqoid != InvalidOid);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1ddf443..8cd3614 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"
@@ -65,6 +66,8 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
@@ -163,6 +166,7 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_constraint; /* for attach partition validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ bool is_partition, List **supOids, List **supconstr,
+ int *supOidCount);
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);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -466,7 +478,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
*/
ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress)
+ ObjectAddress *typaddress, const char *queryString)
{
char relname[NAMEDATALEN];
Oid namespaceId;
@@ -597,6 +609,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
+ stmt->partbound != NULL,
&inheritOids, &old_constraints, &parentOidCount);
/*
@@ -607,18 +620,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
descriptor = BuildDescForRelation(schema);
/*
- * Notice that we allow OIDs here only for plain tables, even though some
- * other relkinds can support them. This is necessary because the
- * default_with_oids GUC must apply only to plain tables and not any other
- * relkind; doing otherwise would break existing pg_dump files. We could
- * allow explicit "WITH OIDS" while not allowing default_with_oids to
- * affect other relkinds, but it would complicate interpretOidsOption().
+ * Notice that we allow OIDs here only for plain tables and partitioned
+ * tables, even though some other relkinds can support them. This is
+ * necessary because the default_with_oids GUC must apply only to plain
+ * tables and not any other relkind; doing otherwise would break existing
+ * pg_dump files. We could allow explicit "WITH OIDS" while not allowing
+ * default_with_oids to affect other relkinds, but it would complicate
+ * interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
(relkind == RELKIND_RELATION ||
relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
+ if (stmt->partbound)
+ {
+ /* If the parent has OIDs, partitions must have them too. */
+ if (parentOidCount > 0 && !localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table without OIDs as partition of table with OIDs")));
+ /* If the parent doesn't, partitions must not have them. */
+ if (parentOidCount == 0 && localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+ }
+
/*
* Find columns with default values and prepare for insertion of the
* defaults. Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition bound, if any. */
+ if (stmt->partbound)
+ {
+ Node *bound;
+ ParseState *pstate;
+ Oid parentId = linitial_oid(inheritOids);
+ Relation parentRel;
+
+ /* Already have strong enough lock on the parent */
+ parentRel = heap_open(parentId, NoLock);
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of parentRel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* Tranform the bound values */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+ bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+ heap_close(parentRel, NoLock);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(rel, bound);
+
+ /*
+ * The code that follows may also update the pg_class tuple to update
+ * relnumchecks, so bump up the command counter to avoid the "already
+ * updated by self" error.
+ */
+ CommandCounterIncrement();
+ }
+
/*
* Process the partitioning specification (if any) and store the
* partition key information into the catalog.
@@ -1117,6 +1190,10 @@ ExecuteTruncate(TruncateStmt *stmt)
relids = lappend_oid(relids, childrelid);
}
}
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("must truncate child tables too")));
}
/*
@@ -1423,6 +1500,7 @@ storage_name(char c)
* of ColumnDef's.) It is destructively changed.
* 'supers' is a list of names (as RangeVar nodes) of parent relations.
* 'relpersistence' is a persistence type of the table.
+ * 'is_partition' tells if the table is a partition
*
* Output arguments:
* 'supOids' receives a list of the OIDs of the parent relations.
@@ -1474,7 +1552,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ bool is_partition, List **supOids, List **supconstr,
+ int *supOidCount)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1484,6 +1563,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
+ List *saved_schema = NIL;
/*
* Check for and reject tables with too many columns. We perform this
@@ -1503,6 +1583,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
MaxHeapAttributeNumber)));
/*
+ * In case of a partition, there are no new column definitions, only
+ * column options specified using the WITH OPTIONS clauses. We merge
+ * those options with actual column definitions after we have finished
+ * generating them from the parent's schema.
+ */
+ if (is_partition)
+ {
+ saved_schema = schema;
+ schema = NIL;
+ }
+
+ /*
* Check for duplicate names in the explicit list of attributes.
*
* Although we might consider merging such entries in the same way that we
@@ -1582,18 +1674,35 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * We do not allow partitioned tables and partitions to participate
+ * in regular inheritance.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
parent->relname)));
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1712,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation \"%s\"",
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation \"%s\""
+ : "cannot create as partition of temporary relation \"%s\"",
parent->relname)));
/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1722,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
!relation->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation of another session")));
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation of another session"
+ : "cannot create as partition of temporary relation of another session")));
/*
* We should have an UNDER permission flag for this, but for now,
@@ -1858,7 +1971,8 @@ 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.
+ * columns into the inherited schema list. Although, we never have any
+ * explicitly declared columns if the table is a partition.
*/
if (inhSchema != NIL)
{
@@ -1887,6 +2001,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
newcollid;
/*
+ * Partitions have only one parent, so conflict should never
+ * occur
+ */
+ Assert(!is_partition);
+
+ /*
* Yes, try to merge the two column definitions. They must
* have the same type, typmod, and collation.
*/
@@ -1968,6 +2088,56 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
/*
+ * Now that we have the column definition list for a partition, we can
+ * check whether the columns referenced in column option specifications
+ * actually exist. Also, we merge the options into the corresponding
+ * column definitions.
+ */
+ if (is_partition && list_length(saved_schema) > 0)
+ {
+ schema = list_concat(schema, saved_schema);
+
+ foreach(entry, schema)
+ {
+ ColumnDef *coldef = lfirst(entry);
+ ListCell *rest = lnext(entry);
+ ListCell *prev = entry;
+
+ /*
+ * Partition column option that does not belong to a column from
+ * the parent. This works because the columns from the parent
+ * come first in the list (see above).
+ */
+ if (coldef->typeName == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist",
+ coldef->colname)));
+ while (rest != NULL)
+ {
+ ColumnDef *restdef = lfirst(rest);
+ ListCell *next = lnext(rest); /* need to save it in case
+ * we delete it */
+
+ if (strcmp(coldef->colname, restdef->colname) == 0)
+ {
+ /*
+ * 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;
+ list_delete_cell(schema, rest, prev);
+ }
+ prev = rest;
+ rest = next;
+ }
+ }
+ }
+
+ /*
* If we found any conflicting parent default values, check to make sure
* they were overridden by the child.
*/
@@ -3129,6 +3299,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);
@@ -3240,12 +3415,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepDropNotNull(rel, recurse, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepSetNotNull(rel, recurse, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3623,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);
@@ -3516,7 +3699,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, we did
+ * not modify anything about it that will change its toasting
+ * requirement, so no need to check.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ tab->partition_constraint == NIL) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3765,6 +3955,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);
@@ -3950,7 +4146,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull ||
+ tab->partition_constraint != NIL)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4030,6 +4227,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4094,6 +4292,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_constraint)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_constraint,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4490,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("partition constraint is violated by some row")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4692,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4802,6 +5015,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5248,6 +5466,20 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be dropped from child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !recurse && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+}
static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
@@ -5323,6 +5555,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
+ /* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
/*
* Okay, actually perform the catalog change ... if needed
*/
@@ -5355,6 +5604,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
* Return the address of the modified column. If the column was already NOT
* NULL, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be added to the child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !recurse && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be added to child tables too")));
+}
+
static ObjectAddress
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6178,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
Relation attr_rel;
ListCell *child;
+ /*
+ * In case of a partitioned table, the column must be dropped from the
+ * partitions as well.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column must be dropped from child tables too")));
+
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(child, children)
{
@@ -7916,6 +8189,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
}
/*
+ * In case of a partitioned table, the constraint must be dropped from
+ * the partitions too. There is no such thing as NO INHERIT constraints
+ * in case of partitioned tables.
+ */
+ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+
+ /*
* 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.
@@ -10217,6 +10500,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10517,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;
@@ -10279,37 +10562,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
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);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10601,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);
@@ -10358,16 +10678,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;
}
/*
@@ -10418,7 +10730,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
@@ -10436,12 +10748,17 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool child_is_partition = false;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ /* If parent_rel is a partitioned table, child_rel must be a partition */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ child_is_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10806,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
* later on, this change will just roll back.)
*/
childatt->attinhcount++;
+
+ /*
+ * In case of partitions, we must enforce that value of attislocal
+ * is same in all partitions. (Note: there are only inherited
+ * attributes in partitions)
+ */
+ if (child_is_partition)
+ {
+ Assert(childatt->attinhcount == 1);
+ childatt->attislocal = false;
+ }
+
simple_heap_update(attrrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrrel, tuple);
heap_freetuple(tuple);
@@ -10511,7 +10840,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.
@@ -10530,10 +10859,15 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool child_is_partition = false;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ /* If parent_rel is a partitioned table, child_rel must be a partition */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ child_is_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10610,6 +10944,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (child_is_partition)
+ {
+ Assert(child_con->coninhcount == 1);
+ child_con->conislocal = false;
+ }
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10634,6 +10980,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10647,13 +11033,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];
@@ -10662,19 +11046,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool child_is_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ /* If parent_rel is a partitioned table, child_rel must be a partition */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ child_is_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11060,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);
@@ -10705,11 +11081,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 (child_is_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
@@ -10718,7 +11103,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)))
@@ -10780,7 +11165,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);
@@ -10811,7 +11196,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)
@@ -10823,30 +11208,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;
}
/*
@@ -12531,3 +12906,401 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * 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)
+{
+ PartitionKey key = RelationGetPartitionKey(rel);
+ Relation attachRel,
+ catalog;
+ List *childrels;
+ TupleConstr *attachRel_constr;
+ List *partConstraint,
+ *existConstraint;
+ SysScanDesc scan;
+ ScanKeyData skey;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ bool skip_validate = false;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+ /* A partition can only have one parent */
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /*
+ * attachRel should not already be part of inheritance; either as a child
+ * table...
+ */
+ catalog = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+ NULL, 1, &skey);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach inheritance child as partition")));
+ systable_endscan(scan);
+
+ /* ...or be a RELKIND_RELATION parent table */
+ ScanKeyInit(&skey,
+ Anum_pg_inherits_inhparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+ 1, &skey);
+ if (HeapTupleIsValid(systable_getnext(scan)) &&
+ attachRel->rd_rel->relkind == RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach regular inheritance parent as partition")));
+ systable_endscan(scan);
+ heap_close(catalog, AccessShareLock);
+
+ /*
+ * Prevent circularity by seeing if rel is a partition of attachRel.
+ * (In particular, this disallows making a rel a partition of itself.)
+ */
+ childrels = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessShareLock, NULL);
+ if (list_member_oid(childrels, RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("circular inheritance not allowed"),
+ errdetail("\"%s\" is already a child of \"%s\".",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(attachRel))));
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("New partition should contain only the columns present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(attachRel, cmd->bound);
+
+ /*
+ * Generate partition constraint from the partition bound specification.
+ * If the parent itself is a partition, make sure to include its
+ * constraint as well.
+ */
+ partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+ cmd->bound),
+ RelationGetPartitionQual(rel, true));
+ partConstraint = (List *) eval_const_expressions(NULL,
+ (Node *) partConstraint);
+ partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+ partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+ /*
+ * Check if we can do away with having to scan the table being attached
+ * to validate the partition constraint by *proving* that the existing
+ * constraints of the table would anyway *imply* the partition constraint.
+ * We include check constraints and NOT NULL constraints in the list of
+ * constraints we'll be using for the proof.
+ */
+ tupleDesc = RelationGetDescr(attachRel);
+ attachRel_constr = tupleDesc->constr;
+ existConstraint = NIL;
+ if (attachRel_constr > 0)
+ {
+ int num_check = attachRel_constr->num_check;
+ int i;
+ Bitmapset *not_null_attrs = NULL;
+
+ if (attachRel_constr->has_not_null)
+ {
+ int natts = attachRel->rd_att->natts;
+
+ for (i = 1; i <= natts; i++)
+ {
+ Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+ if (att->attnotnull && !att->attisdropped)
+ {
+ NullTest *ntest = makeNode(NullTest);
+
+ ntest->arg = (Expr *) makeVar(1,
+ i,
+ att->atttypid,
+ att->atttypmod,
+ att->attcollation,
+ 0);
+ ntest->nulltesttype = IS_NOT_NULL;
+
+ /*
+ * argisrow=false is correct even for a composite column,
+ * because attnotnull does not represent a SQL-spec IS NOT
+ * NULL test in such a case, just IS DISTINCT FROM NULL.
+ */
+ ntest->argisrow = false;
+ ntest->location = -1;
+ existConstraint = lappend(existConstraint, ntest);
+ not_null_attrs = bms_add_member(not_null_attrs, i);
+ }
+ }
+ }
+
+ for (i = 0; i < num_check; i++)
+ {
+ Node *cexpr;
+
+ /*
+ * If this constraint hasn't been fully validated yet, we must
+ * ignore it here.
+ */
+ if (!attachRel_constr->check[i].ccvalid)
+ continue;
+
+ cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+ /*
+ * Run each expression through const-simplification and
+ * canonicalization. It is necessary, because we will be
+ * comparing it to similarly-processed qual clauses, and may fail
+ * to detect valid matches without this.
+ */
+ cexpr = eval_const_expressions(NULL, cexpr);
+ cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+ existConstraint = list_concat(existConstraint,
+ make_ands_implicit((Expr *) cexpr));
+ }
+
+ existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+ /* And away we go ... */
+ if (predicate_implied_by(partConstraint, existConstraint))
+ skip_validate = true;
+
+ /*
+ * We choose to err on the safer side, ie, give up on skipping
+ * the validation scan if the table is to be a range partition and it
+ * does not have NOT NULL constraint on the partition key columns.
+ */
+ if (key->strategy == PARTITION_STRATEGY_RANGE)
+ {
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (!bms_is_member(partattno, not_null_attrs))
+ {
+ skip_validate = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (skip_validate)
+ elog(NOTICE, "skipping scan to validate partition constraint");
+
+ /*
+ * Set up to have the table to be scanned to validate the partition
+ * constraint (see partConstraint above). If it's a partitioned table,
+ * we instead schdule its leaf partitions to be scanned instead.
+ */
+ if (!skip_validate)
+ {
+ List *all_parts;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessExclusiveLock, NULL);
+ else
+ all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ foreach(lc, all_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid part_relid = lfirst_oid(lc);
+ Relation part_rel;
+ Expr *constr;
+
+ /* Lock already taken */
+ if (part_relid != RelationGetRelid(attachRel))
+ part_rel = heap_open(part_relid, NoLock);
+ else
+ part_rel = attachRel;
+
+ /*
+ * Skip if it's a partitioned table. Only RELKIND_RELATION
+ * relations (ie, leaf partitions) need to be scanned.
+ */
+ if (part_rel != attachRel &&
+ part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(part_rel, NoLock);
+ continue;
+ }
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, part_rel);
+
+ constr = linitial(partConstraint);
+ tab->partition_constraint = make_ands_implicit((Expr *) constr);
+
+ /* keep our lock until commit */
+ if (part_rel != attachRel)
+ heap_close(part_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that the new partition is now included
+ * in rel's partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
/*
* Finally create the relation. This also creates the type.
*/
- DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+ DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+ NULL);
return address;
}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
- address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+ address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+ NULL);
Assert(address.objectId != InvalidOid);
return address;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+ PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(listdatums);
+ COPY_NODE_FIELD(lowerdatums);
+ COPY_NODE_FIELD(upperdatums);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+ PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+ COPY_SCALAR_FIELD(infinite);
+ COPY_NODE_FIELD(value);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundSpec:
+ retval = _copyPartitionBoundSpec(from);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _copyPartitionRangeDatum(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 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(listdatums);
+ COMPARE_NODE_FIELD(lowerdatums);
+ COMPARE_NODE_FIELD(upperdatums);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+ COMPARE_SCALAR_FIELD(infinite);
+ COMPARE_NODE_FIELD(value);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundSpec:
+ retval = _equalPartitionBoundSpec(a, b);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _equalPartitionRangeDatum(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
+ case T_PartitionBoundSpec:
+ loc = ((const PartitionBoundSpec *) expr)->location;
+ break;
+ case T_PartitionRangeDatum:
+ loc = ((const PartitionRangeDatum *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUND");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(listdatums);
+ WRITE_NODE_FIELD(lowerdatums);
+ WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+ WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+ WRITE_BOOL_FIELD(infinite);
+ WRITE_NODE_FIELD(value);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundSpec:
+ _outPartitionBoundSpec(str, obj);
+ break;
+ case T_PartitionRangeDatum:
+ _outPartitionRangeDatum(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+ READ_LOCALS(PartitionBoundSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_NODE_FIELD(listdatums);
+ READ_NODE_FIELD(lowerdatums);
+ READ_NODE_FIELD(upperdatums);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+ READ_LOCALS(PartitionRangeDatum);
+
+ READ_BOOL_FIELD(infinite);
+ READ_NODE_FIELD(value);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONBOUND", 14))
+ return_value = _readPartitionBoundSpec();
+ else if (MATCH("PARTRANGEDATUM", 14))
+ return_value = _readPartitionRangeDatum();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2387df9..b458e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionSpec *partspec;
+ PartitionRangeDatum *partrange_datum;
}
%type <node> stmt schema_stmt
@@ -551,6 +552,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partbound_datum
+%type <list> partbound_datum_list
+%type <partrange_datum> PartitionRangeDatum
+%type <list> range_datum_list
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -576,7 +584,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
@@ -592,7 +600,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
@@ -2378,6 +2387,31 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ 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;
+ }
;
alter_column_default:
@@ -2473,6 +2507,73 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partbound_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatums = $5;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ n->lowerdatums = $5;
+ n->upperdatums = $9;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+partbound_datum:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partbound_datum_list:
+ partbound_datum { $$ = list_make1($1); }
+ | partbound_datum_list ',' partbound_datum
+ { $$ = lappend($1, $3); }
+ ;
+
+range_datum_list:
+ PartitionRangeDatum { $$ = list_make1($1); }
+ | range_datum_list ',' PartitionRangeDatum
+ { $$ = lappend($1, $3); }
+ ;
+
+PartitionRangeDatum:
+ UNBOUNDED
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = true;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | partbound_datum
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = false;
+ n->value = $1;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
/*****************************************************************************
*
@@ -2890,6 +2991,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2935,6 +3074,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2957,6 +3101,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2968,6 +3123,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4715,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;
+ }
;
/*****************************************************************************
@@ -13804,6 +14006,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13850,6 +14053,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4175ef5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ 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 transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
/*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ 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;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3045,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of rel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* tranform the values */
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+ cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+ *result_spec;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+
+ result_spec = copyObject(spec);
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListCell *cell;
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ result_spec->listdatums = NIL;
+ foreach(cell, spec->listdatums)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+ ListCell *cell2;
+ bool duplicate;
+
+ value = (Node *) make_const(pstate, &con->val, con->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(pstate,
+ exprLocation((Node *) con))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ /* Don't add to the result if the value is a duplicate */
+ duplicate = false;
+ foreach(cell2, result_spec->listdatums)
+ {
+ Const *value2 = (Const *) lfirst(cell2);
+
+ if (equal(value, value2))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+
+ result_spec->listdatums = lappend(result_spec->listdatums,
+ value);
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ ListCell *cell1,
+ *cell2;
+ int i,
+ j;
+ char *colname;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+ if (list_length(spec->lowerdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("FROM must specify exactly one value per partitioning column")));
+ if (list_length(spec->upperdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("TO must specify exactly one value per partitioning column")));
+
+ i = j = 0;
+ result_spec->lowerdatums = result_spec->upperdatums = NIL;
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum,
+ *rdatum;
+ Node *value;
+ A_Const *lcon = NULL,
+ *rcon = NULL;
+
+ ldatum = (PartitionRangeDatum *) lfirst(cell1);
+ rdatum = (PartitionRangeDatum *) lfirst(cell2);
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ if (!ldatum->infinite)
+ lcon = (A_Const *) ldatum->value;
+ if (!rdatum->infinite)
+ rcon = (A_Const *) rdatum->value;
+
+ if (lcon)
+ {
+ value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ ldatum->value = value;
+ }
+
+ if (rcon)
+ {
+ value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ rdatum->value = value;
+ }
+
+ result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+ copyObject(ldatum));
+ result_spec->upperdatums = lappend(result_spec->upperdatums,
+ copyObject(rdatum));
+
+ ++i;
+ }
+
+ break;
+ }
+ }
+
+ return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_RELATION,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_FOREIGN_TABLE,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
CreateForeignTable((CreateForeignTableStmt *) stmt,
address.objectId);
EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2d16ea..eb16f70 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? If the partitioning structure did not change, that is,
+ * no partitions were added or removed to the relation, the oids array
+ * should still match element-by-element.
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The logic to iterate over
+ * the collections is local to partition.c.
+ */
+ if (pdesc1->boundinfo != NULL)
+ {
+ if (pdesc2->boundinfo == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key,
+ pdesc1->boundinfo, pdesc2->boundinfo))
+ return false;
+ }
+ else if (pdesc2->boundinfo != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2608,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 */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
RelationBuildPartitionKey(relation);
Assert(relation->rd_partkey != NULL);
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
restart = true;
}
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
Oid *partopclass,
Oid *partcollation);
extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
#endif /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..062de88
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds. It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection boundinfo; /* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2);
+
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress);
+ ObjectAddress *typaddress, const char *queryString);
extern void RemoveRelations(DropStmt *drop);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundSpec,
+ T_PartitionRangeDatum,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d30c82b..427eff2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+ NodeTag type;
+
+ char strategy;
+
+ /* List partition values */
+ List *listdatums;
+
+ /*
+ * Range partition lower and upper bounds; each member of the lists
+ * is a PartitionRangeDatum (see below).
+ */
+ List *lowerdatums;
+ List *upperdatums;
+
+ int location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+ NodeTag type;
+
+ bool infinite;
+ Node *value;
+
+ int location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1577,7 +1622,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
@@ -1803,7 +1850,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 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)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+ Node *bound);
#endif /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 60d8de3..cd7ea1d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -602,6 +605,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fb492ad..7201b12 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,243 @@ ERROR: cannot inherit from partitioned table "partitioned"
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, foo;
+-- ATTACH PARTITION
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 FROM (1) TO (10);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+ ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR: cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR: cannot attach regular inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table child
+-- check that 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 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 that the table being attached has only columns present 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: New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child 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: child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing constraint "check_a"
+-- check that the constraint 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: child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+ERROR: partition constraint is violated by some row
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+ERROR: partition constraint is violated by some row
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+-- if we add a valid constraint to the table that only allows rows satisfying
+-- the desired partition constraint, the scan to check the rows will be
+-- skipped based on that constraint
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ALTER TABLE part_3 ADD CONSTRAINT partition_check_a check (a IN (3));
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+NOTICE: skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (1);
+ERROR: "part_3" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ERROR: circular inheritance not allowed
+DETAIL: "part_3" is already a child of "list_parted".
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+ERROR: circular inheritance not allowed
+DETAIL: "list_parted" is already a child of "list_parted".
+-- DETACH PARTITION
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check that the partition being detached is actually 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, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+ attinhcount | attislocal
+-------------+------------
+ 0 | t
+ 0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+ coninhcount | conislocal
+-------------+------------
+ 0 | t
+(1 row)
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ERROR: column must be added to child tables too
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+ERROR: column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+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 inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ERROR: cannot rename inherited column "b"
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+ERROR: cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ERROR: constraint must be added to child tables too
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ERROR: constraint must be added to child tables too
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_1"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ERROR: constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+ERROR: constraint must be dropped from child tables too
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+NOTICE: skipping scan to validate partition constraint
+ALTER TABLE list_parted DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_1
+drop cascades to table part_3
+drop cascades to table part_3_a
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0f15c98..01124e1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
Partition key: LIST ((a + 1))
DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR: specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR: TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR: cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR: cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR: cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR: partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR: partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR: partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 14 other objects
+DETAIL: drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..3f3855b 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,203 @@ ALTER TABLE foo INHERIT partitioned;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, foo;
+
+-- ATTACH PARTITION
+
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that 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 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 that the table being attached has only columns present 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 that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+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 that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the new partition won't contain rows violating partition constraint
+INSERT INTO fail_part VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); -- fail
+
+-- check the same things as above, but now the table being attached is itself partitioned
+-- so its child tables are scanned
+CREATE TABLE part_3 (
+ a int NOT NULL,
+ b char(2) NOT NULL COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+) PARTITION BY LIST (b);
+CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a');
+INSERT INTO part_3_a (a, b) VALUES (4, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); -- fail
+
+-- delete the faulting row and all will be ok
+DELETE FROM part_3_a WHERE a NOT IN (3);
+INSERT INTO part_3_a (a, b) VALUES (3, 'a');
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- if we add a valid constraint to the table that only allows rows satisfying
+-- the desired partition constraint, the scan to check the rows will be
+-- skipped based on that constraint
+ALTER TABLE list_parted DETACH PARTITION part_3;
+ALTER TABLE part_3 ADD CONSTRAINT partition_check_a check (a IN (3));
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (1);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_3 ATTACH PARTITION list_parted FOR VALUES IN ('b');
+ALTER TABLE list_parted ATTACH PARTITION list_parted FOR VALUES IN (0);
+
+-- DETACH PARTITION
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted DETACH PARTITION not_a_part;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted DETACH PARTITION part_3;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a';
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted ADD COLUMN c int;
+ALTER TABLE ONLY list_parted DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_1 ADD COLUMN c text;
+ALTER TABLE part_1 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_1 RENAME COLUMN b to c;
+ALTER TABLE part_1 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted ALTER b SET NOT NULL;
+ALTER TABLE part_1 ALTER b DROP NOT NULL;
+ALTER TABLE part_1 DROP CONSTRAINT check_a;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted DROP CONSTRAINT check_a;
+
+-- check that a partition cannot 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 drop or alter type of partition key columns of lower level partitioned tables
+-- for example, part_3 is partitioned on b;
+ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3);
+ALTER TABLE list_parted DROP COLUMN b;
+ALTER TABLE list_parted ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f100498..683b852 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
\d partitioned2
DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions 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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-13.patch (22.1K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-13.patch)
download | inline diff:
From 16322c06ccdf69bc9c63f3c019030196eb604f28 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 | 82 +++++++++++++++++++
src/bin/pg_dump/common.c | 86 ++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 118 ++++++++++++++++++++++++++--
src/bin/pg_dump/pg_dump.h | 12 +++
src/bin/psql/describe.c | 85 +++++++++++++++++---
src/test/regress/expected/create_table.out | 40 ++++++++++
src/test/regress/sql/create_table.sql | 12 +++
7 files changed, 415 insertions(+), 20 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundSpec:
+ {
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ switch (spec->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->listdatums != NIL);
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->lowerdatums != NIL &&
+ spec->upperdatums != NIL &&
+ list_length(spec->lowerdatums) ==
+ list_length(spec->upperdatums));
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " FROM");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->lowerdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ appendStringInfoString(buf, " TO");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->upperdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ break;
+
+ default:
+ elog(ERROR, "unrecognized partition strategy: %d",
+ (int) spec->strategy);
+ break;
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 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);
@@ -323,6 +338,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
*
@@ -924,6 +976,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 c443735..9deb035 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5595,9 +5595,16 @@ getInherits(Archive *fout, int *numInherits)
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
- /* find all the inheritance information */
-
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ /*
+ * Find all the inheritance information, excluding implicit inheritance
+ * via partitioning. We handle that case using getPartitions(), because
+ * we want more information about partitions than just the parent-child
+ * relationship.
+ */
+ 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);
@@ -5624,6 +5631,70 @@ 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;
+
+ /* Before version 10, there are no partitions */
+ if (fout->remoteVersion < 100000)
+ {
+ *numPartitions = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find the inheritance and boundary information about partitions */
+
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -14155,6 +14226,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 */
@@ -14183,8 +14265,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;
@@ -14213,7 +14298,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");
}
@@ -14271,15 +14357,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 (");
@@ -14449,6 +14542,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 527d6ad..3f3c777 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;
@@ -625,6 +636,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 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2535,8 +2563,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)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ 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 level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+ Table "public.part_b"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+ "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+ Table "public.part_c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
-- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
-- create a level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Teach-a-few-places-to-use-partition-check-quals-13.patch (30.9K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/6-0005-Teach-a-few-places-to-use-partition-check-quals-13.patch)
download | inline diff:
From c6e9257003a3a7f0109eedfc13362f70d2d76bbc 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 quals.
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 | 3 +-
src/backend/executor/execMain.c | 75 +++++++++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/optimizer/util/plancat.c | 20 +++
src/include/nodes/execnodes.h | 4 +
src/test/regress/expected/inherit.out | 271 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 82 ++++++++++
src/test/regress/expected/update.out | 27 +++
src/test/regress/sql/inherit.sql | 48 ++++++
src/test/regress/sql/insert.sql | 59 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 608 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
else
{
/* 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 9773272..ea3f59a 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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,28 @@ 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 for relation \"%s\" violates partition constraint",
+ 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 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,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)
@@ -922,7 +922,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 ad07baa..a2cbf14 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"
@@ -1139,6 +1140,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.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition predicates, if any */
+ pcqual = RelationGetPartitionQual(relation, true);
+ 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 f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_cd
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_null
+ Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+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');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- 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 from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (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 for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL: Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR: new row for relation "part_ee_ff_1_10" violates partition constraint
+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 6 other objects
+DETAIL: drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to 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..bdb4e2c 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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 for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- 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);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- 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 from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (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..5392fa5 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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
[text/x-diff] 0006-Introduce-a-PartitionTreeNode-data-structure-13.patch (8.0K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/7-0006-Introduce-a-PartitionTreeNode-data-structure-13.patch)
download | inline diff:
From c2a8ec719729249c67caf5fa68912a4a2d2c0dcd 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 | 208 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 213 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7810c9d..6301d91 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -113,6 +113,61 @@ typedef struct PartitionListValue
int index;
} PartitionListValue;
+/*
+ * 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_parts 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;
+
static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
@@ -123,6 +178,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
static List *generate_partition_qual(Relation rel, bool recurse);
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -873,6 +931,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 tree that are themselves partitioned tables.
+ */
+ root = GetPartitionTreeNodeRecurse(rel, 0);
+ root->index = 0; /* Root table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids(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(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, ptnode->pdesc->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1313,6 +1418,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 062de88..057b1e3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -36,6 +36,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
extern void RelationBuildPartitionDesc(Relation relation);
extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +46,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
extern Oid get_partition_parent(Oid relid);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0007-Tuple-routing-for-partitioned-tables-13.patch (42.6K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/8-0007-Tuple-routing-for-partitioned-tables-13.patch)
download | inline diff:
From c5e1a7c993f82b9faa1773ceed1d01b99115140d 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 | 325 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 203 +++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 47 +++++-
src/backend/executor/nodeModifyTable.c | 142 ++++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 77 ++++++++
src/backend/optimizer/util/plancat.c | 13 ++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 52 +++++
src/test/regress/sql/insert.sql | 25 +++
18 files changed, 914 insertions(+), 7 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 6301d91..e3a0848 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -181,6 +181,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -194,6 +206,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
static bool equal_range_info(PartitionKey key,
PartitionRangeInfo *r1, PartitionRangeInfo *r2);
static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg);
static bool partition_rbound_eq(PartitionKey key,
PartitionRangeBound *b1, PartitionRangeBound *b2);
typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1437,7 +1451,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;
@@ -1521,6 +1535,281 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ PartitionTreeNode prev;
+
+ /*
+ * Index returned above considers only this parent (of which partRel
+ * is a partition). We however want to return the index across the
+ * the whole partition tree. If partRel is the leftmost partition
+ * of parent (index = 0) or if none of the left siblings are
+ * partitioned tables, we can simply return parent's offset plus
+ * partRel's index as the result. Otherwise, we find the node
+ * corresponding to the rightmost partitioned table among partRel's
+ * left siblings and use the information therein.
+ */
+ 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);
+
+ index = node->offset + node->num_leaf_parts +
+ (index - node->index - 1);
+ }
+ else
+ /*
+ * The easy case where all of partRel's left siblings (if any) are
+ * leaf partitions.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to recurse as the selected partition is a partitioned table
+ * itself. Locate the corresponding PartitionTreeNode to pass 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)
+{
+ PartitionListInfo listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+
+ if (isnull && listinfo.has_null)
+ return listinfo.null_index;
+ else if (!isnull)
+ {
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ value);
+ if (found >= 0)
+ return listinfo.indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ 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)
+{
+ int offset;
+ PartitionRangeInfo rangeinfo;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ offset = partition_rbound_bsearch(key,
+ rangeinfo.bounds, rangeinfo.nbounds,
+ tuple, partition_rbound_datum_cmp,
+ true, NULL);
+
+ /*
+ * Offset returned is such that the bound at offset is found to be
+ * less or equal with the tuple. That is, the tuple belongs to the
+ * partition with the rangeinfo.bounds[offset] as the lower bound and
+ * rangeinfo.bounds[offset+1] as the upper bound, provided the latter
+ * is indeed marked !lower (that is, it's an upper bound). If it turns
+ * out that it is a lower bound then the corresponding index will be -1,
+ * which means no valid partition exists.
+ */
+ return rangeinfo.indexes[offset+1];
+}
+
/* List partition related support functions */
/*
@@ -1767,6 +2056,40 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
}
/*
+ * Return whether bound <=, =, >= partition key of tuple
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg)
+{
+ Datum *datums1 = bound->datums,
+ *datums2 = (Datum *) arg;
+ int i;
+ int32 cmpval;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ if (bound->infinite[i])
+ return bound->lower ? -1 : 1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /* If datums are equal and this is an upper bound, tuple > bound */
+ if (cmpval == 0 && !bound->lower)
+ return -1;
+
+ return cmpval;
+}
+
+/*
* Return whether two range bounds are equal simply by comparing datums
*/
static bool
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..6fb376f 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.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ List *leaf_parts;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_part_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_parts = get_leaf_partition_oids(cstate->ptnode);
+ num_leaf_parts = list_length(leaf_parts);
+
+ 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_part_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_parts)
+ {
+ Relation part_rel;
+
+ part_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(part_rel, CMD_INSERT);
+
+ InitResultRelInfo(leaf_part_rri,
+ part_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_part_rri, false);
+
+ /* Special dance for foreign tables */
+ if (leaf_part_rri->ri_FdwRoutine)
+ {
+ List *fdw_private;
+
+ fdw_rte->relid = RelationGetRelid(part_rel);
+ fdw_private = leaf_part_rri->ri_FdwRoutine->PlanForeignModify(root,
+ plan,
+ 1,
+ 0);
+ fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+ }
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(part_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(part_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_part_rri++;
+ i++;
+ }
+
+ cstate->partition_fdw_priv_lists = fdw_private_lists;
+ pfree(fdw_rte);
+ pfree(plan);
+ pfree(parse);
+ pfree(root);
+ }
}
else
{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1705,6 +1801,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);
}
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2281,6 +2395,7 @@ CopyFrom(CopyState cstate)
* only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
{
@@ -2391,6 +2506,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2534,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;
@@ -2439,10 +2556,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 */
@@ -2494,6 +2647,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 */
@@ -2523,7 +2701,16 @@ CopyFrom(CopyState cstate)
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)
@@ -2553,7 +2740,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,
@@ -2577,6 +2765,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2590,7 +2784,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 8cd3614..b51d59a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,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 ea3f59a..62bf8b7 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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);
@@ -2996,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 a612b08..5c2bf6c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -272,6 +273,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;
/*
@@ -511,6 +537,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
@@ -1565,6 +1597,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)));
@@ -1655,6 +1688,98 @@ 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_TABLE)
+ {
+ int i,
+ j,
+ num_leaf_parts;
+ List *leaf_parts;
+ ListCell *cell;
+ ResultRelInfo *leaf_part_rri;
+
+ mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+ leaf_parts = get_leaf_partition_oids(mtstate->mt_partition_tree_root);
+ num_leaf_parts = list_length(leaf_parts);
+
+ 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_part_rri = mtstate->mt_partitions;
+ i = j = 0;
+ foreach(cell, leaf_parts)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation part_rel;
+
+ part_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(part_rel, CMD_INSERT);
+
+ InitResultRelInfo(leaf_part_rri,
+ part_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_part_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_part_rri, false);
+
+ if (leaf_part_rri->ri_FdwRoutine)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+ leaf_part_rri,
+ fdw_private,
+ 0,
+ eflags);
+ j++;
+ }
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(part_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(part_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_part_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1972,6 +2097,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 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_inherits_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ /*
+ * We are only interested in foreign tables. Note that this will
+ * also eliminate any partitioned tables since foreign tables can
+ * only ever be leaf partitions.
+ */
+ if (!oid_is_foreign_table(myoid))
+ continue;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 057b1e3..86cc598 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/rel.h"
@@ -50,4 +52,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
extern List *get_leaf_partition_oids(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 136276b..c62946f 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 ff8b66b..ce01008 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"
@@ -1147,6 +1148,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ 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)
+
+-- ok
+insert into list_parted values (null, 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_null | | 0
+ part_null | | 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
+(8 rows)
+
-- cleanup
drop table range_parted cascade;
NOTICE: drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ 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;
+
+-- ok
+insert into list_parted values (null, 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
[text/x-diff] 0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-13.patch (24.7K, ../../8d7c35e3-1c85-33d0-4440-0a75bf9d31cd@lab.ntt.co.jp/9-0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-13.patch)
download | inline diff:
From fbdc4f916d5d8138eee71641d4418aa756f3b944 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 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,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</>.
@@ -2793,12 +2793,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>
@@ -2842,59 +2845,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 >= 100 AND outletID < 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>
@@ -2911,8 +2877,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>
@@ -2940,7 +2908,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
@@ -2971,12 +2939,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 FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('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 FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
</programlisting>
Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3024,110 +2965,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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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">
@@ -3145,22 +2995,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
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
were created above:
<programlisting>
-CREATE TABLE measurement_y2008m02 (
- CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
</programlisting>
As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 FROM ('2017-07-01') TO ('2017-08-01');
</programlisting>
</para>
</sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
<programlisting>
SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= DATE '2017-01-01';
</programlisting>
Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate >= 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>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
</programlisting>
Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3397,9 +3173,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>
@@ -3423,18 +3199,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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-11 11:49 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-11 11:49 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
I have not looked at the latest set of patches, but in the version
that I have we create one composite type for every partition. This
means that if there are thousand partitions, there will be thousand
identical entries in pg_type. Since all the partitions share the same
definition (by syntax), it doesn't make sense to add so many identical
entries. Moreover, in set_append_rel_size(), while translating the
expressions from parent to child, we add a ConvertRowtypeExpr instead
of whole-row reference if reltype of the parent and child do not match
(adjust_appendrel_attrs_mutator())
if (appinfo->parent_reltype != appinfo->child_reltype)
{
ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
I guess, we should set reltype of child to that of parent for
declarative partitions.
On Fri, Nov 11, 2016 at 4:00 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/11/11 6:51, Robert Haas wrote:
>> On Wed, Nov 9, 2016 at 9:58 PM, Amit Langote
>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>> With all patches applied, "make check" fails with a bunch of diffs
>>>> that look like this:
>>>>
>>>> Check constraints:
>>>> - "pt1chk2" CHECK (c2 <> ''::text)
>>>> "pt1chk3" CHECK (c2 <> ''::text)
>>>
>>> Hm, I can't seem to reproduce this one. Is it perhaps possible that you
>>> applied the patches on top of some other WIP patches or something?
>>
>> Nope. I just checked and this passes with only 0001 and 0002 applied,
>> but when I add 0003 and 0004 then it starts failing.
>
> Sorry, it definitely wasn't an error on your part.
>
>> It appears that
>> the problem starts at this point in the foreign_data test:
>>
>> ALTER TABLE pt1 DROP CONSTRAINT pt1chk2 CASCADE;
>>
>> After that command, in the expected output, pt1chk2 stops showing up
>> in the output of \d+ pt1, but continues to appear in the output of \d+
>> ft2. With your patch, however, it stops showing up for ft2 also. If
>> that's not also happening for you, it might be due to an uninitialized
>> variable someplace.
>
> Thanks for the context. I think I found the culprit variable in
> MergeConstraintsIntoExisting() and fixed it. As you correctly guessed,
> the uninitialized variable caused (in your environment) even non-partition
> child relations to be treated partitions and hence forced any merged
> constraints to be non-local in all cases, not just in case of partitions.
> Which meant the command you quoted would even drop the ft2's (a child)
> constraint because its conislocal is wrongly false.
>
>>
>> + /* Force inheritance recursion, if partitioned table. */
>>
>> Doesn't match code (any more).
>
> Fixed.
>
>>>> I think "partitioning key" is a bit awkward and actually prefer
>>>> "partiton key". But "partition method" sounds funny so I would go
>>>> with "partitioning method".
>>>
>>> OK, "partition key" and "partitioning method" it is then. Source code
>>> comments, error messages, variables call the latter (partitioning)
>>> "strategy" though which hopefully is fine.
>>
>> Oh, I like "partitioning strategy". Can we standardize on that?
>
> OK, done.
>
>>>> I would be in favor of committing the initial patch set without that,
>>>> and then considering the possibility of adding it later. If we
>>>> include it in the initial patch set we are stuck with it.
>>>
>>> OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
>>> with each of the range bounds.
>>>
>>> I haven't changed any code (such as comparison functions) that manipulates
>>> instances of PartitionRangeBound which has a flag called inclusive. I
>>> didn't remove the flag, but is instead just set to (is_lower ? true :
>>> false) when initializing from the parse node. Perhaps, there is some scope
>>> for further simplifying that code, which you probably alluded to when you
>>> proposed that we do this.
>>
>> Yes, you need to rip out all of the logic that supports it. Having
>> the logic to support it but not the syntax is bad because then that
>> code can't be properly tested.
>
> Agreed, done.
>
>
> Attached updated patches.
>
> Thanks,
> Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-14 05:16 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-14 05:16 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
I forgot to quote your comments in the email I sent on Friday [1], with
new patches that do take care of the following comments.
On 2016/11/11 4:04, Robert Haas wrote:
> On Thu, Nov 10, 2016 at 7:40 AM, Amit Langote
>>
>> OK, "partition key" and "partitioning method" it is then. Source code
>> comments, error messages, variables call the latter (partitioning)
>> "strategy" though which hopefully is fine.
>
> Oh, I like "partitioning strategy". Can we standardize on that?
Done.
>> OK, I have removed the syntactic ability to specify INCLUSIVE/EXCLUSIVE
>> with each of the range bounds.
>>
>> I haven't changed any code (such as comparison functions) that manipulates
>> instances of PartitionRangeBound which has a flag called inclusive. I
>> didn't remove the flag, but is instead just set to (is_lower ? true :
>> false) when initializing from the parse node. Perhaps, there is some scope
>> for further simplifying that code, which you probably alluded to when you
>> proposed that we do this.
>
> Yes, you need to rip out all of the logic that supports it. Having
> the logic to support it but not the syntax is bad because then that
> code can't be properly tested.
Agreed and done. Now the only thing that dictates the inclusivity of
individual range bounds during comparisons (with other range bounds or
partition key of tuples) is whether the bound is a lower bound or not;
inclusive if, exclusive if not.
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/8d7c35e3-1c85-33d0-4440-0a75bf9d31cd%40lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-14 06:31 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-14 06:31 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@alvh.no-ip.org>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/04 0:49, Robert Haas wrote:
> On Thu, Nov 3, 2016 at 7:46 AM, <alvherre@alvh.no-ip.org> wrote:
>> El 2016-10-28 07:53, Amit Langote escribió:
>>> @@ -6267,6 +6416,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab,
>>> Relation rel,
>>> * Validity checks (permission checks wait till we have the column
>>> * numbers)
>>> */
>>> + if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>>> + ereport(ERROR,
>>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>>> + errmsg("cannot reference relation
>>> \"%s\"", RelationGetRelationName(pkrel)),
>>> + errdetail("Referencing partitioned tables
>>> in foreign key constraints is not supported.")));
>>
>> Is there a plan for fixing this particular limitation? It's a pretty
>> serious problem for users,
>> and the suggested workaround (to create a separate non-partitioned table
>> which carries only the PK
>> columns which is updated by triggers, and direct the FKs to it instead of to
>> the partitioned table)
>> is not only a very ugly one, but also very slow.
>
> If you have two compatibly partitioned tables, and the foreign key
> matches the partitioning keys, you could implement a foreign key
> between the two tables as a foreign key between each pair of matching
> partitions. Otherwise, isn't the only way to handle this a global
> index?
I am assuming you don't mean a global index (on partitioned tables) as in
some new kind of monolithic physical structure that implements the
constraint across tables (partitions), right? I'm thinking you mean a
collection of btree indexes on individual partitions with the key of each
index matching the partition key of the parent, created internally as part
of the creation of the same index on the parent. In fact, the said
indexes are created and maintained sort of like how inherited attributes,
constraints are. That would require quite a bit of new infrastructure.
We did discuss about the possibility of such a feature being implemented
on top of declarative partitioning, but not in version 1 [1].
Thanks,
Amit
[1]
https://www.postgresql.org/message-id/CA%2BTgmoZZMfcf16YaHuhP1Vk%3Dj8PDFeHCvfj%2BFJQd%2BeFhs%2B7P8A%...
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-15 09:51 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-15 09:51 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
On 2016/11/11 20:49, Ashutosh Bapat wrote:
> I have not looked at the latest set of patches, but in the version
> that I have we create one composite type for every partition. This
> means that if there are thousand partitions, there will be thousand
> identical entries in pg_type. Since all the partitions share the same
> definition (by syntax), it doesn't make sense to add so many identical
> entries. Moreover, in set_append_rel_size(), while translating the
> expressions from parent to child, we add a ConvertRowtypeExpr instead
> of whole-row reference if reltype of the parent and child do not match
> (adjust_appendrel_attrs_mutator())
> if (appinfo->parent_reltype != appinfo->child_reltype)
> {
> ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
>
> I guess, we should set reltype of child to that of parent for
> declarative partitions.
Thanks for the suggestion. I agree that partitions having the same
reltype as the parent will help a number of cases including the one you
mentioned, but I haven't yet considered how invasive such a change will be.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-15 10:30 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-15 10:30 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/11 19:30, Amit Langote wrote:
>
> Attached updated patches.
Here is the latest version of the patches with some fixes along with those
mentioned below (mostly in 0003):
- Fixed the logic to skip the attach partition validation scan such that
it won't skip scanning a list partition *that doesn't accept NULLs* if
the partition key column is not set NOT NULL (it similarly doesn't skip
scanning a range partition if either of the partition key columns is not
set NOT NULL, because a range partition key cannot contain NULLs at all)
- Added some more regression tests for ATTACH PARTITION command
- Some fixes to documentation and source code comments
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Catalog-and-DDL-for-partitioned-tables-14.patch (115.3K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/2-0001-Catalog-and-DDL-for-partitioned-tables-14.patch)
download | inline diff:
From 9471d830fa2a34f51a778d06be85937f82e463d7 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.
In addition to a catalog for storing the partitioning information, this
commit also adds a new relkind to pg_class.h.
PARTITION BY clause is added to CREATE TABLE. The tables so created are
RELKIND_PARTITIONED_TABLE relations which are special in number of ways,
especially their interactions with table inheritance features.
---
doc/src/sgml/catalogs.sgml | 112 +++++++-
doc/src/sgml/ref/create_table.sgml | 57 ++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/dependency.c | 10 +-
src/backend/catalog/heap.c | 166 ++++++++++-
src/backend/catalog/index.c | 4 +-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 24 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 5 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 450 +++++++++++++++++++++++++++-
src/backend/commands/trigger.c | 14 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/nodes/copyfuncs.c | 34 ++
src/backend/nodes/equalfuncs.c | 29 ++
src/backend/nodes/outfuncs.c | 28 ++
src/backend/parser/gram.y | 105 ++++++-
src/backend/parser/parse_agg.c | 10 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_utilcmd.c | 68 +++++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/rewrite/rowsecurity.c | 3 +-
src/backend/utils/cache/relcache.c | 268 ++++++++++++++++-
src/backend/utils/cache/syscache.c | 12 +
src/include/catalog/dependency.h | 3 +-
src/include/catalog/heap.h | 10 +
src/include/catalog/indexing.h | 3 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned_table.h | 69 +++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 29 ++
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/include/utils/rel.h | 68 +++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/alter_table.out | 43 +++
src/test/regress/expected/create_table.out | 158 ++++++++++
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/alter_table.sql | 29 ++
src/test/regress/sql/create_table.sql | 143 +++++++++
52 files changed, 1952 insertions(+), 56 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_table.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..6139ab1 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-table"><structname>pg_partitioned_table</structname></link></entry>
+ <entry>information about partition key of tables</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
- <literal>r</> = ordinary table, <literal>i</> = index,
+ <literal>r</> = ordinary table, <literal>P</> = partitioned table,
+ <literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@@ -4689,6 +4695,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-table">
+ <title><structname>pg_partitioned_table</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-table">
+ <primary>pg_partitioned_table</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned_table</structname> stores
+ information about how tables are partitioned.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_table</> 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 partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy; <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 part of the 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, 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>partcollation</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
+ the collation to use for partitioning.
+ </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>partattrs</>. 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..1a95219 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,41 @@ 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</literal> clause specifies a strategy
+ of partitioning the table. The table thus created is called a
+ <firstterm>partitioned</firstterm> table. The parenthesized list of
+ columns or expressions forms the <firstterm>partition key</firstterm>
+ for the table. When using range partitioning, the partition key can
+ include multiple columns or expressions, but for list partitioning, the
+ partition key must consist of a single column or expression. If no
+ btree operator class is specified when creating a partitioned table,
+ the default btree operator class for the datatype will be used. If
+ there is none, an error will be reported.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions),
+ which are created using separate <literal>CREATE TABLE</> commands.
+ The partitioned table is itself empty. A data row inserted into the
+ table is routed to a partition based on the value of columns or
+ expressions in the partition key. If no existing partition matches
+ the values in the new row, an error will be reported.
+ </para>
+
+ <para>
+ Partitioned tables do not support <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ <literal>FOREIGN KEY</literal> constraints; however, you can define
+ these constraints on individual partitions.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -1369,6 +1406,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 83a97b0..34018ca 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..362deca 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..8a4ac7e 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..9746f24 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1393,7 +1393,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior)
+ DependencyType self_behavior,
+ bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
- recordMultipleDependencies(depender,
- self_addrs->refs, self_addrs->numrefs,
- self_behavior);
+ if (!ignore_self)
+ recordMultipleDependencies(depender,
+ self_addrs->refs, self_addrs->numrefs,
+ self_behavior);
free_object_addresses(self_addrs);
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..754a08b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -48,6 +48,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1135,6 +1138,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1179,6 +1183,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1354,7 +1359,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_TABLE);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1801,6 +1807,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_table tuple.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -2033,6 +2045,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
attNos = NULL;
/*
+ * Partitioned tables do not contain any rows themselves, so a NO INHERIT
+ * constraint makes no sense.
+ */
+ if (is_no_inherit &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
+
+ /*
* Create the Check Constraint
*/
constrOid =
@@ -3018,3 +3041,138 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * StorePartitionKey
+ * Store information about the partition key rel into the catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ oidvector *partcollation_vec;
+ Datum partexprDatum;
+ Relation pg_partitioned_table;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_table];
+ bool nulls[Natts_pg_partitioned_table];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ /* Copy the partition attribute numbers, opclass OIDs into arrays */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+ partcollation_vec = buildoidvector(partcollation, partnatts);
+
+ /* Convert the expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprString;
+
+ exprString = nodeToString(partexprs);
+ partexprDatum = CStringGetTextDatum(exprString);
+ pfree(exprString);
+ }
+ else
+ partexprDatum = (Datum) 0;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprDatum)
+ nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
+ values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
+
+ simple_heap_insert(pg_partitioned_table, tuple);
+
+ /* Update the indexes on pg_partitioned_table */
+ CatalogUpdateIndexes(pg_partitioned_table, tuple);
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+
+ /* Mark this relation as dependent on a few things as follows */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class and collation per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = partcollation[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Anything mentioned in the expressions. We must ignore the column
+ * references, which will depend on the table itself; there is no
+ * separate partition key object.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_AUTO, true);
+
+ /*
+ * We must invalidate the relcache so that the next
+ * CommandCounterIncrement() will cause the same to be rebuilt using the
+ * information in just created catalog entry.
+ */
+ CacheInvalidateRelcache(rel);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned_table entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..08b0989 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO);
+ DEPENDENCY_AUTO, false);
}
}
else
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d531d17..bb4b080 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 8fabe68..724b41e 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL);
+ DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c617abb..c4db6f7 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1317,6 +1318,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3c81906..28b6f63 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..9735bb2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Identify the opclass to use.
*/
- classOidP[attn] = GetIndexOpClass(attribute->opclass,
- atttype,
- accessMethodName,
- accessMethodId);
+ classOidP[attn] = ResolveOpClass(attribute->opclass,
+ atttype,
+ accessMethodName,
+ accessMethodId);
/*
* Identify the exclusion operator, if any.
@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
+ *
+ * Note: This is used to resolve operator class specification in index and
+ * partition key definition.
*/
-static Oid
-GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId)
+Oid
+ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a0c0d75..9e62e00 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index d694cf8..1757428 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
@@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id)
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
rel = heap_open(relid, AccessExclusiveLock);
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..10268be 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fc3a8ee..e08fd5d 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1475,6 +1475,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f97bee5..1ddf443 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -65,6 +65,7 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -216,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_TABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("table \"%s\" does not exist"),
+ gettext_noop("table \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a table"),
+ gettext_noop("Use DROP TABLE to remove a table.")},
{RELKIND_SEQUENCE,
ERRCODE_UNDEFINED_TABLE,
gettext_noop("sequence \"%s\" does not exist"),
@@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr);
+static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
+static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation);
/* ----------------------------------------------------------------
@@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
+ if (stmt->partspec != NULL)
+ {
+ if (relkind != RELKIND_RELATION)
+ elog(ERROR, "unexpected relkind: %d", (int) relkind);
+
+ relkind = RELKIND_PARTITIONED_TABLE;
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
@@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -698,6 +718,36 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
rel = relation_open(relationId, AccessExclusiveLock);
/*
+ * Process the partitioning specification (if any) and store the
+ * partition key information into the catalog.
+ */
+ if (stmt->partspec)
+ {
+ char strategy;
+ int partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ Oid partcollation[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ /*
+ * We need to transform the raw parsetrees corresponding to partition
+ * expressions into executable expression trees. Like column defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
+ stmt->partspec = transformPartitionSpec(rel, stmt->partspec,
+ &strategy);
+ ComputePartitionAttrs(rel, stmt->partspec->partParams,
+ partattrs, &partexprs, partopclass,
+ partcollation);
+
+ partnatts = list_length(stmt->partspec->partParams);
+ StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
+ partopclass, partcollation);
+ }
+
+ /*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
@@ -926,7 +976,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
{
HeapTuple tuple;
struct DropRelationCallbackState *state;
- char relkind;
+ char relkind,
+ expected_relkind;
Form_pg_class classform;
LOCKMODE heap_lockmode;
@@ -955,7 +1006,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
+ * but RemoveRelations() can only pass one relkind for a given relation.
+ * It chooses RELKIND_RELATION for both regular and partitioned tables.
+ * That means we must be careful before giving the wrong type error when
+ * the relation is RELKIND_PARTITIONED_TABLE.
+ */
+ if (classform->relkind == RELKIND_PARTITIONED_TABLE)
+ expected_relkind = RELKIND_RELATION;
+ else
+ expected_relkind = classform->relkind;
+
+ if (relkind != expected_relkind)
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1293,7 +1356,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1521,6 +1585,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2233,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4291,6 +4363,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4600,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5491,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5692,6 +5767,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
}
/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *used_in_expr if attnum is found to be referenced in some partition
+ * key expression. It's possible for a column to be both used directly and
+ * as part of an expression; if that happens, *used_in_expr may end up as
+ * either true or false. That's OK for current uses of this function, because
+ * *used_in_expr is only used to tailor the error message text.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr)
+{
+ PartitionKey key;
+ int partnatts;
+ List *partexprs;
+ ListCell *partexprs_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ return false;
+
+ key = RelationGetPartitionKey(rel);
+ partnatts = get_partition_natts(key);
+ partexprs = get_partition_exprs(key);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partattno = get_partition_col_attnum(key, i);
+
+ if (partattno != 0)
+ {
+ if (attnum == partattno)
+ {
+ if (used_in_expr)
+ *used_in_expr = false;
+ return true;
+ }
+ }
+ else
+ {
+ /* Arbitrary expression */
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+
+ /* Find all attributes referenced */
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ {
+ if (used_in_expr)
+ *used_in_expr = true;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
* Return value is the address of the dropped column.
*/
static ObjectAddress
@@ -5705,6 +5842,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 +5887,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in the partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6418,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference partitioned table \"%s\"",
+ RelationGetRelationName(pkrel))));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7886,6 +8043,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7916,6 +8074,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in the partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7931,7 +8102,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8958,6 +9130,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9420,6 +9593,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9842,7 +10016,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10041,6 +10216,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10092,6 +10272,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11481,6 +11668,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11930,7 +12118,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12084,6 +12272,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_TABLE &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12095,3 +12284,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * Transform any expressions present in the partition key
+ */
+static PartitionSpec *
+transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
+{
+ PartitionSpec *newspec;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ newspec = (PartitionSpec *) makeNode(PartitionSpec);
+
+ newspec->strategy = partspec->strategy;
+ newspec->location = partspec->location;
+ newspec->partParams = NIL;
+
+ /* Parse partitioning strategy name */
+ if (!pg_strcasecmp(partspec->strategy, "list"))
+ *strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp(partspec->strategy, "range"))
+ *strategy = PARTITION_STRATEGY_RANGE;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partitioning strategy \"%s\"",
+ partspec->strategy)));
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partspec->partParams)
+ {
+ ListCell *lc;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... (foo, foo) */
+ foreach(lc, newspec->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(lc);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears more than once in partition key",
+ pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+ }
+
+ newspec->partParams = lappend(newspec->partParams, pelem);
+ }
+
+ return newspec;
+}
+
+/*
+ * Compute per-partition-column information from a list of PartitionElem's
+ */
+static void
+ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass, Oid *partcollation)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid attcollation;
+
+ if (pelem->name != NULL)
+ {
+ /* Simple attribute reference */
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ attcollation = attform->attcollation;
+ ReleaseSysCache(atttuple);
+
+ /* Note that whole-row references can't happen here; see below */
+ }
+ else
+ {
+ /* Expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+ attcollation = exprCollation(expr);
+
+ /*
+ * Strip any top-level COLLATE clause. This ensures that we treat
+ * "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(expr, CollateExpr))
+ expr = (Node *) ((CollateExpr *) expr)->arg;
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ Bitmapset *expr_attrs = NULL;
+
+ partattrs[attn] = 0; /* marks the column as expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * Note that expression_planner does not change the passed in
+ * expression destructively and we have already saved the
+ * expression to be stored into the catalog above.
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ /*
+ * Partition expression cannot contain mutable functions,
+ * because a given row must always map to the same partition
+ * as long as there is no change in the partition boundary
+ * structure.
+ */
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+
+ /*
+ * While it is not exactly *wrong* for an expression to be
+ * a constant value, it seems better to prevent such input.
+ */
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use constant expression as partition key")));
+
+ /*
+ * transformPartitionSpec() should have already rejected subqueries,
+ * aggregates, window functions, and SRFs, based on the EXPR_KIND_
+ * for partition expressions.
+ */
+
+ /* Cannot have expressions containing whole-row references */
+ pull_varattnos(expr, 1, &expr_attrs);
+ if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ expr_attrs))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition key expressions cannot contain whole-row references")));
+ }
+ }
+
+ /*
+ * Apply collation override if any
+ */
+ if (pelem->collation)
+ attcollation = get_collation_oid(pelem->collation, false);
+
+ /*
+ * Check we have a collation iff it's a collatable type. The only
+ * expected failures here are (1) COLLATE applied to a noncollatable
+ * type, or (2) partition expression had an unresolved collation.
+ * But we might as well code this to be a complete consistency check.
+ */
+ if (type_is_collatable(atttype))
+ {
+ if (!OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("could not determine which collation to use for partition expression"),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+ }
+ else
+ {
+ if (OidIsValid(attcollation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be(atttype))));
+ }
+
+ partcollation[attn] = attcollation;
+
+ /*
+ * Identify a btree opclass to use. Currently, we use only btree
+ * operators, which seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(partopclass[attn]))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
+ }
+ else
+ partopclass[attn] = ResolveOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ attn++;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c264b7..98de9d7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1210,6 +1218,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 58bbf55..efa5200 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..9773272 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index efb0c5e..0668462 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1886,6 +1886,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 04e49b7..1c978c0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionSpec *
+_copyPartitionSpec(const PartitionSpec *from)
+{
+
+ PartitionSpec *newnode = makeNode(PartitionSpec);
+
+ COPY_STRING_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(collation);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5104,6 +5132,12 @@ copyObject(const void *from)
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
+ case T_PartitionSpec:
+ retval = _copyPartitionSpec(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2eaf41c..7d0391d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true;
}
+static bool
+_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
+{
+ COMPARE_STRING_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(collation);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3401,6 +3424,12 @@ equal(const void *a, const void *b)
case T_TriggerTransition:
retval = _equalTriggerTransition(a, b);
break;
+ case T_PartitionSpec:
+ retval = _equalPartitionSpec(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 748b687..323daf5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionSpec(StringInfo str, const PartitionSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_STRING_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(collation);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition:
_outTriggerTransition(str, obj);
break;
+ case T_PartitionSpec:
+ _outPartitionSpec(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..2387df9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionSpec *partspec;
}
%type <node> stmt schema_stmt
@@ -545,6 +547,10 @@ 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 <partspec> PartitionSpec OptPartitionSpec
+%type <str> part_strategy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -2812,69 +2818,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3419,6 +3431,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key specification */
+OptPartitionSpec: PartitionSpec { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
+ {
+ PartitionSpec *n = makeNode(PartitionSpec);
+
+ n->strategy = $3;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_strategy: IDENT { $$ = $1; }
+ | unreserved_keyword { $$ = pstrdup($1); }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->collation = $2;
+ n->opclass = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_collate opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->collation = $4;
+ n->opclass = $5;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..92d1577 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expression");
+ else
+ err = _("grouping operations are not allowed in partition key expression");
+
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("window functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 63f7965..031d827 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("cannot use subquery in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 56c9a42..7d9b415 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_EXPRESSION:
+ err = _("set-returning functions are not allowed in partition key expression");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc2..666cc1f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -760,6 +826,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..8d28634 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 65c3d6e..a8fc636 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1231,6 +1231,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e029116..2871adc 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
*hasSubLinks = false;
/* If this is not a normal relation, just return immediately */
- if (rte->relkind != RELKIND_RELATION)
+ if (rte->relkind != RELKIND_RELATION &&
+ rte->relkind != RELKIND_PARTITIONED_TABLE)
return;
/* Switch to checkAsUser if it's set */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..a2d16ea 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
+#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@@ -49,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -431,6 +435,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_TABLE:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -796,6 +801,239 @@ RelationBuildRuleLock(Relation relation)
}
/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it survives
+ * as long as the relcache. To avoid leaking memory in that context in case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
+ *
+ * Also, since the structure being created here is sufficiently complex, we
+ * make a private child context of CacheMemoryContext for each relation that
+ * has associated partition key information. That means no complicated logic
+ * to free individual elements whenever the relcache entry is flushed - just
+ * delete the context.
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned_table form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ AttrNumber *attrs;
+ oidvector *opclass;
+ oidvector *collation;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext partkeycxt,
+ oldcxt;
+
+ tuple = SearchSysCache1(PARTRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * The following happens when we have created our pg_class entry but not
+ * the pg_partitioned_table entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ /* Fixed-length attributes */
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+ attrs = form->partattrs.values;
+
+ /*
+ * To retrieve further variable-length attributes, we'd need the catalog's
+ * tuple descriptor
+ */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+
+ /* Operator class */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ /* Collation */
+ datum = fastgetattr(tuple, Anum_pg_partitioned_table_partcollation,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ collation = (oidvector *) DatumGetPointer(datum);
+
+ /* Expressions */
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_table_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprString;
+ Node *expr;
+
+ exprString = TextDatumGetCString(datum);
+ expr = stringToNode(exprString);
+ pfree(exprString);
+
+ /*
+ * Run the expressions through const-simplification since the planner
+ * will be comparing them to similarly-processed qual clause operands,
+ * and may fail to detect valid matches without this step. We don't
+ * need to bother with canonicalize_qual() though, because partition
+ * expressions are not full-fledged qualification clauses.
+ */
+ expr = eval_const_expressions(NULL, (Node *) expr);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) expr);
+ key->partexprs = (List *) expr;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Gather type and collation info as well */
+ key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
+ key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /* Copy partattrs and fill other per-attribute info */
+ memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ /* Collect opfamily information */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * A btree support function covers the cases of list and range methods
+ * currently supported.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+
+ /* Collation */
+ key->partcollation[i] = collation->values[i];
+
+ /* Collect type information */
+ if (attno != 0)
+ {
+ key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ key->parttypid[i] = exprType(lfirst(partexprs_item));
+ key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
+ key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
+ }
+ get_typlenbyvalalign(key->parttypid[i],
+ &key->parttyplen[i],
+ &key->parttypbyval[i],
+ &key->parttypalign[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ partkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_SIZES);
+ relation->rd_partkeycxt = partkeycxt;
+ oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
+ relation->rd_partkey = copy_partition_key(key);
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * copy_partition_key
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+copy_partition_key(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+ int n;
+
+ newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = n = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+
+ newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
+
+ newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
+
+ newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
+
+ newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
+ memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
+
+ newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
+ memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
+
+ newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
+
+ newkey->parttypalign = (char *) palloc(n * sizeof(bool));
+ memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
+
+ newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
+ memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
+
+ return newkey;
+}
+
+/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
@@ -1050,6 +1288,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ RelationBuildPartitionKey(relation);
+ else
+ {
+ relation->rd_partkeycxt = NULL;
+ relation->rd_partkey = NULL;
+ }
+
/*
* if it's an index, initialize index-related information
*/
@@ -2042,6 +2289,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ if (relation->rd_partkeycxt)
+ MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2983,7 +3232,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -3514,6 +3765,17 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4267,6 +4529,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
+ result = (List *) canonicalize_qual((Expr *) result);
+
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@@ -5035,6 +5299,8 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeycxt = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a3e0517 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_table_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..960a697 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
- DependencyType self_behavior);
+ DependencyType self_behavior,
+ bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..11b16a9 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
+/* pg_partitioned_table catalog manipulation functions */
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass,
+ Oid *partcollation);
+extern void RemovePartitionKeyByRelId(Oid relid);
+
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..40f7576 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..ba0f745 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h
new file mode 100644
index 0000000..5f0dc7b
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_table.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_table.h
+ * definition of the system "partitioned table" relation
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_TABLE_H
+#define PG_PARTITIONED_TABLE_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_table definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_table
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrat; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partition key */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of columns in the
+ * partition key */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ oidvector partcollation; /* user-specified collation for keys */
+ pg_node_tree partexprs; /* list of expressions in the partitioning
+ * key; one item for each zero entry in
+ * partattrs[] */
+#endif
+} FormData_pg_partitioned_table;
+
+/* ----------------
+ * Form_pg_partitioned_table corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_table relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
+
+/* ----------------
+ * compiler constants for pg_partitioned_table
+ * ----------------
+ */
+#define Natts_pg_partitioned_table 7
+#define Anum_pg_partitioned_table_partrelid 1
+#define Anum_pg_partitioned_table_partstrat 2
+#define Anum_pg_partitioned_table_partnatts 3
+#define Anum_pg_partitioned_table_partattrs 4
+#define Anum_pg_partitioned_table_partclass 5
+#define Anum_pg_partitioned_table_partcollation 6
+#define Anum_pg_partitioned_table_partexprs 7
+
+#endif /* PG_PARTITIONED_TABLE_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 2b894ff..d790fbf 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid ResolveOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cb9307c..b27412c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -454,6 +454,8 @@ typedef enum NodeTag
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
+ T_PartitionElem,
+ T_PartitionSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 04b1c2f..d30c82b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,34 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a column in the partition key
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *collation; /* name of collation; NIL = default */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionSpec - partition key specification
+ */
+typedef struct PartitionSpec
+{
+ NodeTag type;
+ char *strategy; /* partitioning strategy ('list' or 'range') */
+ List *partParams; /* List of PartitionElems */
+ int location; /* token location, or -1 if unknown */
+} PartitionSpec;
+
+#define PARTITION_STRATEGY_LIST 'l'
+#define PARTITION_STRATEGY_RANGE 'r'
/****************************************************************************
* Nodes for a Query tree
@@ -1775,6 +1803,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 6633586..bd6dc02 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa15f28..60d8de3 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -45,6 +45,35 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
+/*
+ * Information about the partition key of a relation
+ */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partitioning strategy */
+ int16 partnatts; /* number of columns in the partition key */
+ AttrNumber *partattrs; /* attribute numbers of columns in the
+ * partition key */
+ List *partexprs; /* list of expressions in the partitioning
+ * key, or NIL */
+
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+
+ /* Partitioning collation per attribute */
+ Oid *partcollation;
+
+ /* Type information per attribute */
+ Oid *parttypid;
+ int32 *parttypmod;
+ int16 *parttyplen;
+ bool *parttypbyval;
+ char *parttypalign;
+ Oid *parttypcoll;
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@@ -94,6 +123,9 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ MemoryContext rd_partkeycxt; /* private memory cxt for the below */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -534,6 +566,42 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns the PartitionKey of a relation
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
+/*
+ * PartitionKey inquiry functions
+ */
+static inline int
+get_partition_strategy(PartitionKey key)
+{
+ return key->strategy;
+}
+
+static inline int
+get_partition_natts(PartitionKey key)
+{
+ return key->partnatts;
+}
+
+static inline List *
+get_partition_exprs(PartitionKey key)
+{
+ return key->partexprs;
+}
+
+/*
+ * PartitionKey inquiry functions - one column
+ */
+static inline int16
+get_partition_col_attnum(PartitionKey key, int col)
+{
+ return key->partattrs[col];
+}
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..39fe947 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index cf9f6d3..fb492ad 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2974,3 +2974,46 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
c4 | integer | | |
DROP TABLE test_add_column;
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
+ ^
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ ^
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ ^
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+ ^
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter type of column named in partition key
+ALTER TABLE partitioned DROP COLUMN b;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+ERROR: cannot alter type of column referenced in partition key expression
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ERROR: cannot change inheritance of partitioned table
+ALTER TABLE foo INHERIT partitioned;
+ERROR: cannot inherit from partitioned table "partitioned"
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..e555076 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,161 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- Partitioned tables
+--
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+ERROR: cannot create partitioned table as inheritance child
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot list partition using more than one column
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pkrel(a)
+ ^
+DROP TABLE pkrel;
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+ERROR: unique constraints are not supported on partitioned tables
+LINE 2: a int UNIQUE
+ ^
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 3: EXCLUDE USING gist (a WITH &&)
+ ^
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+ERROR: column "a" appears more than once in partition key
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+ERROR: set-returning functions are not allowed in partition key expression
+DROP FUNCTION retset(int);
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+ERROR: cannot use subquery in partition key expression
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use constant expression as partition key
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+ERROR: cannot use constant expression as partition key
+DROP FUNCTION const_func();
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+ERROR: unrecognized partitioning strategy "hash"
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+ERROR: column "b" named in partition key does not exist
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+ERROR: cannot use system column "xmin" in partition key
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+ERROR: functions in partition key expression must be marked IMMUTABLE
+DROP FUNCTION immut_func(int);
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+ERROR: partition key expressions cannot contain whole-row references
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+ERROR: data type point has no default btree operator class
+HINT: You must specify a btree operator class or define a default btree operator class for the data type.
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+ relkind
+---------
+ P
+(1 row)
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+ERROR: cannot drop function plusone(integer) because other objects depend on it
+DETAIL: table partitioned depends on function plusone(integer)
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+ERROR: cannot inherit from partitioned table "partitioned2"
+DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1ebcf6..8fa929a 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c8eed3e..d929b4d 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1875,3 +1875,32 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
+
+-- unsupported constraint types for partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (a+b+1));
+ALTER TABLE partitioned ADD UNIQUE (a);
+ALTER TABLE partitioned ADD PRIMARY KEY (a);
+ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
+ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
+
+-- cannot drop column that is part of the partition key
+ALTER TABLE partitioned DROP COLUMN a;
+ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
+ALTER TABLE partitioned DROP COLUMN b;
+ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE foo (
+ a int,
+ b int
+);
+ALTER TABLE partitioned INHERIT foo;
+ALTER TABLE foo INHERIT partitioned;
+
+-- cannot add NO INHERIT constraint to partitioned tables
+ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
+
+DROP TABLE partitioned, foo;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..e24ff3f 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,146 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- Partitioned tables
+--
+
+-- cannot combine INHERITS and PARTITION BY (although grammar allows)
+CREATE TABLE partitioned (
+ a int
+) INHERITS (some_table) PARTITION BY LIST (a);
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE partitioned (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- unsupported constraint type for partitioned tables
+CREATE TABLE partitioned (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+CREATE TABLE pkrel (
+ a int PRIMARY KEY
+);
+CREATE TABLE partitioned (
+ a int REFERENCES pkrel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pkrel;
+
+CREATE TABLE partitioned (
+ a int UNIQUE
+) PARTITION BY RANGE (a);
+
+CREATE TABLE partitioned (
+ a int,
+ EXCLUDE USING gist (a WITH &&)
+) PARTITION BY RANGE (a);
+
+-- prevent column from being used twice in the partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (a, a);
+
+-- prevent using prohibited expressions in the key
+CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (retset(a));
+DROP FUNCTION retset(int);
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+
+CREATE TABLE partitioned (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY LIST ((a LIKE (SELECT 1)));
+
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (('a'));
+
+CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (const_func());
+DROP FUNCTION const_func();
+
+-- only accept "list" and "range" as partitioning strategy
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY HASH (a);
+
+-- specified column must be present in the table
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (b);
+
+-- cannot use system columns in partition key
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (xmin);
+
+-- functions in key must be immutable
+CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE (immut_func(a));
+DROP FUNCTION immut_func(int);
+
+-- cannot contain whole-row references
+CREATE TABLE partitioned (
+ a int
+) PARTITION BY RANGE ((partitioned));
+
+-- prevent using columns of unsupported types in key (type must have a btree operator class)
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY LIST (a point_ops);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a);
+CREATE TABLE partitioned (
+ a point
+) PARTITION BY RANGE (a point_ops);
+
+-- cannot add NO INHERIT constraints to partitioned tables
+CREATE TABLE partitioned (
+ a int,
+ CONSTRAINT check_a CHECK (a > 0) NO INHERIT
+) PARTITION BY RANGE (a);
+
+-- some checks after successful creation of a partitioned table
+CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
+
+CREATE TABLE partitioned (
+ a int,
+ b int,
+ c text,
+ d text
+) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
+
+-- check relkind
+SELECT relkind FROM pg_class WHERE relname = 'partitioned';
+
+-- prevent a function referenced in partition key from being dropped
+DROP FUNCTION plusone(int);
+
+-- partitioned table cannot partiticipate in regular inheritance
+CREATE TABLE partitioned2 (
+ a int
+) PARTITION BY RANGE (a);
+CREATE TABLE fail () INHERITS (partitioned2);
+
+DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0002-psql-and-pg_dump-support-for-partitioned-tables-14.patch (23.9K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/3-0002-psql-and-pg_dump-support-for-partitioned-tables-14.patch)
download | inline diff:
From bbe96ba88f247fb9b288436c2f2227844f22a05c 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 | 159 ++++++++++++++++++++++++++++
src/bin/pg_dump/common.c | 4 +
src/bin/pg_dump/pg_dump.c | 66 +++++++++++-
src/bin/pg_dump/pg_dump.h | 2 +
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 | 20 ++++-
src/test/regress/sql/create_table.sql | 6 +-
10 files changed, 301 insertions(+), 26 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a3a4174..9004878 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_table.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,
@@ -1412,6 +1414,163 @@ 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 opt_collation opt_opclass [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+ PRETTYFLAG_INDENT)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+ Form_pg_partitioned_table form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ oidvector *partcollation;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relid);
+
+ form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+
+ Assert(form->partrelid == relid);
+
+ /* Must get partclass and partcollation the hard way */
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partcollation, &isnull);
+ Assert(!isnull);
+ partcollation = (oidvector *) DatumGetPointer(datum);
+
+
+ /*
+ * Get the 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_table_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partitioned_table_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+
+ if (!IsA(partexprs, List))
+ elog(ERROR, "unexpected node type found in partexprs: %d",
+ (int) nodeTag(partexprs));
+
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relid), relid);
+
+ initStringInfo(&buf);
+
+ switch (form->partstrat)
+ {
+ case PARTITION_STRATEGY_LIST:
+ appendStringInfo(&buf, "LIST");
+ break;
+ case PARTITION_STRATEGY_RANGE:
+ appendStringInfo(&buf, "RANGE");
+ break;
+ default:
+ elog(ERROR, "unexpected partition strategy: %d",
+ (int) form->partstrat);
+ }
+
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+ Oid keycolcollation;
+ Oid partcoll;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple attribute reference */
+ char *attname;
+ int32 keycoltypmod;
+
+ attname = get_relid_attribute_name(relid, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ get_atttypetypmodcoll(relid, attnum,
+ &keycoltype, &keycoltypmod,
+ &keycolcollation);
+ }
+ else
+ {
+ /* 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,
+ 0, 0);
+
+ appendStringInfoString(&buf, str);
+ keycoltype = exprType(partkey);
+ keycolcollation = exprCollation(partkey);
+ }
+
+ /* Add collation, if not default for column */
+ partcoll = partcollation->values[keyno];
+ if (OidIsValid(partcoll) && partcoll != keycolcollation)
+ appendStringInfo(&buf, " COLLATE %s",
+ generate_collation_name((partcoll)));
+
+ /* 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/common.c b/src/bin/pg_dump/common.c
index 1cbb987..3e20f02 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -273,6 +273,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading policies\n");
getPolicies(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading partition key information for interesting tables\n");
+ getTablePartitionKeyInfo(fout, tblinfo, numTables);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ee1f673..fb92e7f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1226,9 +1226,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_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2085,6 +2086,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_TABLE)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -4959,7 +4963,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,
@@ -4973,7 +4977,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_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5501,7 +5506,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_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6910,6 +6917,47 @@ getTransforms(Archive *fout, int *numTransforms)
}
/*
+ * getTablePartitionKeyInfo -
+ * for each interesting partitioned table, read information about its
+ * partition key
+ *
+ * modifies tblinfo
+ */
+void
+getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+ PQExpBuffer q = createPQExpBuffer();
+ int i,
+ ntups;
+ PGresult *res;
+
+ /* No partitioned tables before 10 */
+ if (fout->remoteVersion < 100000)
+ return;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &(tblinfo[i]);
+
+ /* Only partitioned tables have partition key */
+ if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tbinfo->dobj.dump)
+ continue;
+
+ resetPQExpBuffer(q);
+ appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
+ ntups = PQntuples(res);
+ Assert(ntups == 1);
+ tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
+ }
+}
+
+/*
* getTableAttrs -
* for each interesting table, read info about its attributes
* (names, types, default values, CHECK constraints, etc)
@@ -14262,6 +14310,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -14322,6 +14373,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -14340,7 +14392,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_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -14557,6 +14610,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
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 642c4d5..f33f86d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -311,6 +311,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.
@@ -649,5 +650,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
+extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
#endif /* PG_DUMP_H */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 1632104..9b08bae 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"), /* partitioned table */
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1566,8 +1568,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)");
}
@@ -1632,6 +1634,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1645,8 +1655,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')
{
headers[cols++] = gettext_noop("Collation");
headers[cols++] = gettext_noop("Nullable");
@@ -1667,12 +1677,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");
}
@@ -1748,7 +1758,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);
@@ -1756,14 +1766,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 (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 */
@@ -1902,7 +1931,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;
@@ -2461,7 +2490,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;
@@ -2665,7 +2694,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
@@ -2993,6 +3022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" c.relname as \"%s\",\n"
" CASE c.relkind"
" WHEN 'r' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" WHEN 'v' THEN '%s'"
" WHEN 'm' THEN '%s'"
" WHEN 'i' THEN '%s'"
@@ -3004,6 +3034,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
+ gettext_noop("table"),
gettext_noop("view"),
gettext_noop("materialized view"),
gettext_noop("index"),
@@ -3048,7 +3079,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 b556c00..9938695 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -452,7 +452,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 */
@@ -483,7 +483,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 */
@@ -513,7 +513,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 17ec71d..74d9447 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1977,6 +1977,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 90f5132..7ed1623 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -728,6 +728,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 e555076..0f15c98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,7 +407,25 @@ HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
ERROR: cannot inherit from partitioned table "partitioned2"
+-- Partition key in describe output
+\d partitioned
+ Table "public.partitioned"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+ c | text | | |
+ d | text | | |
+Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
+
+\d partitioned2
+ Table "public.partitioned2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST ((a + 1))
+
DROP TABLE partitioned, partitioned2;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index e24ff3f..f100498 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -408,7 +408,11 @@ DROP FUNCTION plusone(int);
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
-) PARTITION BY RANGE (a);
+) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
+-- Partition key in describe output
+\d partitioned
+\d partitioned2
+
DROP TABLE partitioned, partitioned2;
--
1.7.1
[text/x-diff] 0003-Catalog-and-DDL-for-partitions-14.patch (208.2K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/4-0003-Catalog-and-DDL-for-partitions-14.patch)
download | inline diff:
From aa739e51bda92fc6a6e1eb319e0a8003e50da548 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.
pg_class gets new fields: relispartition and relpartbound (a pg_node_tree).
Parent-child relationships of a partitioned table and its partitions are
managed with inheritance, so not much here about that.
DDL includes both a way to create new partition and "attach" an existing table
as partition. An existing partition can be "detached" from a table, which
preserves it as a regular (or partitioned) table.
Add a field to relcache for storing a "partition descriptor" of a partitioned
table which has hopefully all the information about a table's partitions that
someone might want to do something with.
---
doc/src/sgml/catalogs.sgml | 17 +
doc/src/sgml/ref/alter_table.sgml | 105 ++-
doc/src/sgml/ref/create_foreign_table.sgml | 26 +
doc/src/sgml/ref/create_table.sgml | 92 ++-
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 105 ++-
src/backend/catalog/partition.c | 1646 ++++++++++++++++++++++++++++
src/backend/commands/createas.c | 2 +-
src/backend/commands/sequence.c | 2 +-
src/backend/commands/tablecmds.c | 1027 ++++++++++++++++--
src/backend/commands/typecmds.c | 3 +-
src/backend/commands/view.c | 3 +-
src/backend/nodes/copyfuncs.c | 47 +
src/backend/nodes/equalfuncs.c | 41 +
src/backend/nodes/nodeFuncs.c | 6 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/nodes/readfuncs.c | 34 +
src/backend/parser/gram.y | 208 ++++-
src/backend/parser/parse_utilcmd.c | 260 +++++-
src/backend/tcop/utility.c | 6 +-
src/backend/utils/cache/relcache.c | 93 ++-
src/include/catalog/heap.h | 1 +
src/include/catalog/partition.h | 48 +
src/include/catalog/pg_class.h | 22 +-
src/include/commands/tablecmds.h | 2 +-
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 52 +-
src/include/parser/kwlist.h | 2 +
src/include/parser/parse_utilcmd.h | 2 +
src/include/utils/rel.h | 21 +
src/test/regress/expected/alter_table.out | 298 +++++
src/test/regress/expected/create_table.out | 187 ++++
src/test/regress/sql/alter_table.sql | 266 +++++
src/test/regress/sql/create_table.sql | 153 +++
34 files changed, 4670 insertions(+), 139 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6139ab1..31352a2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1846,6 +1846,13 @@
</row>
<row>
+ <entry><structfield>relispartition</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if table is a partition</entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
@@ -1891,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings
</entry>
</row>
+
+ <row>
+ <entry><structfield>relpartbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ If table is a partition (see <structfield>relispartition</structfield>),
+ internal representation of the partition bound
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e48ccf2..5949837 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,6 +77,8 @@ ALTER TABLE ALL IN TABLESPACE <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>
+ DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@@ -166,6 +168,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 <literal>NOT NULL</literal> in the parent
+ table.
+ </para>
</listitem>
</varlistentry>
@@ -704,6 +712,52 @@ 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 table (which might itself be partitioned)
+ as a partition of the target table. The partition bound specification
+ must correspond to the partitioning strategy and partition key of the
+ target table. The table to be attached must have all the same columns
+ as the target table and no more; moreover, the column types must also
+ match. Also, it must have all the <literal>NOT NULL</literal> and
+ <literal>CHECK</literal> constraints present in the target table.
+ If some <literal>CHECK</literal> constraint of the table being attached
+ is marked <literal>NO INHERIT</literal>, the command will fail; such
+ constraints must be recreated without the <literal>NO INHERIT</literal>
+ clause. Currently <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
+ and <literal>FOREIGN KEY</literal> constraints are not considered.
+ </para>
+
+ <para>
+ A full table scan is performed on the table being attached to check that
+ no existing row in the table violates the partition constraint. It is
+ possible to avoid this potentially expensive scan by adding a valid
+ <literal>CHECK</literal> constraint to the table that only allows rows
+ satisfying the desired partition constraint before trying to attach.
+ It will be determined using such a constraint that existing rows in the
+ table satisfy the partition constraint, so the table scan to check the
+ same will be skipped. When adding a range partition or a list partition
+ that does not accept <literal>NULL</literal> values, also add
+ <literal>NOT NULL</literal> constraint to the partition key columns,
+ otherwise, the scan will be performed regardless of the existing
+ constraints.
+ </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, but no longer has any
+ ties to the table from which it was detached.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -721,8 +775,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>.
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.
+ To add the table as a new child of a parent table, or as a new partition
+ of a partitioned table, you must own the parent table as well.
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
@@ -938,6 +992,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 or to 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>
@@ -978,6 +1050,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</para>
<para>
+ Similarly, when attaching a new partition it is scanned to verify that
+ existing rows meet the partition constraint.
+ </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.
@@ -1047,6 +1124,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited.
+ A nonrecursive <literal>DROP COLUMN</literal> command will fail for a
+ partitioned table, because all partitions of a table must have the same
+ columns as the partitioning root.
</para>
<para>
@@ -1233,6 +1313,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 FROM ('2016-07-01') TO ('2016-08-01');
+</programlisting></para>
+
+ <para>
+ Attach a partition to list partitioned table:
+<programlisting>
+ALTER TABLE cities
+ ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
+</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..5d0dcf5 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,12 @@ 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.
+ </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 +329,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</>, as a partition
+ of the range partitioned table <structname>measurement</>:
+
+<programlisting>
+CREATE FOREIGN TABLE measurement_y2016m07
+ PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('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 1a95219..54c8495 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 { IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) }
+
<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>] [, ... ] ) ]
@@ -232,6 +247,50 @@ 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.
+ </para>
+
+ <para>
+ The partition bound specification must correspond to the partitioning
+ method and partition key of the parent table, and must not overlap with
+ any existing partition of that 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.
+ Defaults and constraints can optionally be specified for each of the
+ inherited columns. One can also specify table constraints in addition
+ to those inherited from the parent. If a check constraint with the name
+ matching one of the parent's constraint is specified, it is merged with
+ the latter, provided the specified condition is same.
+ </para>
+
+ <para>
+ Rows inserted into a partitioned table will be automatically routed to
+ the correct partition. If no suitable partition exists, an error will
+ occur.
+ </para>
+
+ <para>
+ A partition must have the same column names and types as the table of
+ which it is a partition. Therefore, modifications to the column names
+ or types of the partitioned table will automatically propagate to all
+ children, as will operations such as TRUNCATE which normally affect a
+ table and all of its inheritance children. It is also possible to
+ TRUNCATE a partition individually, just as for an inheritance child.
+ Dropping a partition directly using <literal>DROP TABLE</literal>
+ will fail; it must be <firstterm>detached</> from the parent first.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -1424,7 +1483,38 @@ CREATE TABLE measurement (
CREATE TABLE cities (
name text not null,
population int,
-) PARTITION BY LIST (name);
+) PARTITION BY LIST (initcap(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 FROM ('2016-07-01') TO ('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 Francisco');
+</programlisting></para>
+
+ <para>
+ Create partition of a list partitioned table that is itself further
+ partitioned and then add a partition to it:
+<programlisting>
+CREATE TABLE cities_west
+ PARTITION OF cities (
+ CONSTRAINT city_id_nonzero CHECK (city_id != 0)
+) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
+
+CREATE TABLE cities_west_10000_to_100000
+ PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
</programlisting></para>
</refsect1>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 362deca..2d5ac09 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 754a08b..64fc283 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"
@@ -810,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@@ -821,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
else
nulls[Anum_pg_class_reloptions - 1] = true;
+ /* relpartbound is set by updating this tuple, if necessary */
+ nulls[Anum_pg_class_relpartbound - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
/*
@@ -926,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ /* relispartition is always set by updating this tuple later */
+ new_rel_reltup->relispartition = false;
+
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
@@ -1765,6 +1773,8 @@ void
heap_drop_with_catalog(Oid relid)
{
Relation rel;
+ Oid parentOid;
+ Relation parent = NULL;
/*
* Open and lock the relation.
@@ -1772,6 +1782,21 @@ heap_drop_with_catalog(Oid relid)
rel = relation_open(relid, AccessExclusiveLock);
/*
+ * If the relation is a partition, we must grab exclusive lock on its
+ * parent because we need to update its partition descriptor. We must
+ * take a table lock strong enough to prevent all queries on the parent
+ * from proceeding until we commit and send out a shared-cache-inval
+ * notice that will make them update their partition descriptor.
+ * Sometimes, doing this is cycles spent uselessly, especially if the
+ * parent will be dropped as part of the same command anyway.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ parentOid = get_partition_parent(relid);
+ parent = heap_open(parentOid, AccessExclusiveLock);
+ }
+
+ /*
* There can no longer be anyone *else* touching the relation, but we
* might still have open queries or cursors, or pending trigger events, in
* our own session.
@@ -1862,6 +1887,12 @@ heap_drop_with_catalog(Oid relid)
* delete relation tuple
*/
DeleteRelationTuple(relid);
+
+ if (parent)
+ {
+ CacheInvalidateRelcache(parent);
+ heap_close(parent, NoLock); /* keep the lock */
+ }
}
@@ -2468,8 +2499,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
* definition) then interpret addition of a local constraint as a
* legal merge. This allows ALTER ADD CONSTRAINT on parent and
* child tables to be given in either order with same end state.
+ * However if the relation is a partition, all inherited
+ * constraints are always non-local, including those that were
+ * merged.
*/
- if (is_local && !con->conislocal)
+ if (is_local && !con->conislocal && !rel->rd_rel->relispartition)
allow_merge = true;
if (!found || !allow_merge)
@@ -2514,10 +2548,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
tup = heap_copytuple(tup);
con = (Form_pg_constraint) GETSTRUCT(tup);
- if (is_local)
- con->conislocal = true;
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (rel->rd_rel->relispartition)
+ {
+ con->coninhcount = 1;
+ con->conislocal = false;
+ }
else
- con->coninhcount++;
+ {
+ if (is_local)
+ con->conislocal = true;
+ else
+ con->coninhcount++;
+ }
+
if (is_no_inherit)
{
Assert(is_local);
@@ -3176,3 +3224,52 @@ RemovePartitionKeyByRelId(Oid relid)
ReleaseSysCache(tuple);
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * StorePartitionBound
+ * Update pg_class tuple of rel to store the partition bound and set
+ * relispartition to true
+ */
+void
+StorePartitionBound(Relation rel, Node *bound)
+{
+ Relation classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+#ifdef USE_ASSERT_CHECKING
+ {
+ Form_pg_class classForm;
+ bool isnull;
+
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(!classForm->relispartition);
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(isnull);
+ }
+#endif
+
+ /* Fill in relpartbound value */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
+ new_null[Anum_pg_class_relpartbound - 1] = false;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+ /* Also set the flag */
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..e21e7ad
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,1646 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related data structures and functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "optimizer/var.h"
+#include "rewrite/rewriteManip.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+/*
+ * List bound collection - collection of values of all partitions of a list
+ * partitioned relation
+ *
+ * The values are put in a single array to be able to use binary search
+ * over them. Note that null values are never put into the array.
+ */
+typedef struct PartitionListInfo
+{
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in lists of all the partitions */
+ int *indexes; /* partition index of each individiual value; has
+ * same length as the values array above */
+ bool has_null; /* Is there a partition defined to accept nulls? */
+ int null_index; /* Index of the null-accepting partition */
+} PartitionListInfo;
+
+/* One bound of a range partition */
+typedef struct PartitionRangeBound
+{
+ int index;
+ Datum *datums; /* range bound datums */
+ bool *infinite; /* infinite flag for each datum */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Range bound collection - sorted array of range bounds of the partitions
+ * of a range partitioned table
+ *
+ * In most cases, nbounds is far less than 2 * nparts, because a partition's
+ * upper bound and the next partition's lower bound are same in common cases,
+ * and we only store one of them.
+ *
+ * There are nbound members in the bounds array and nbounds+1 in the indexes
+ * array.
+ */
+typedef struct PartitionRangeInfo
+{
+ PartitionRangeBound **bounds;
+ int nbounds;
+ int *indexes;
+} PartitionRangeInfo;
+
+/*
+ * Collection of bounds of a partitioned relation
+ */
+typedef struct BoundCollectionData
+{
+ char strategy; /* list or range bounds? */
+
+ union
+ {
+ struct PartitionListInfo lists;
+ struct PartitionRangeInfo ranges;
+ } bounds;
+} BoundCollectionData;
+
+/* One value coming from some (index'th) list partition */
+typedef struct PartitionListValue
+{
+ Datum value;
+ int index;
+} PartitionListValue;
+
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+
+static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
+static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel);
+
+static List *generate_partition_qual(Relation rel, bool recurse);
+
+/* List partition related support functions */
+static bool equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2);
+static int32 partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2);
+static int partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe);
+
+/* Range partition related support functions */
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBound *src);
+static bool equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2);
+static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static bool partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2);
+typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
+ PartitionRangeBound *,
+ void *);
+static int partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal);
+
+/*
+ * RelationBuildPartitionDesc
+ * Form rel's partition descriptor
+ *
+ * Not flushed from the cache by RelationClearRelation() unless changed because
+ * of addition or removal of partition.
+ */
+void
+RelationBuildPartitionDesc(Relation rel)
+{
+ List *inhoids,
+ *partoids;
+ Oid *oids = NULL;
+ List *boundspecs = NIL;
+ ListCell *cell;
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+ PartitionDesc result;
+ MemoryContext oldcxt;
+
+ /* List partitioning */
+ PartitionListValue **all_values = NULL;
+ int all_values_count = 0;
+ bool found_null_partition = false;
+ int null_partition_index = -1;
+
+ /* Range partitioning */
+ PartitionRangeBound **distinct_bounds = NULL;
+ int num_distinct_bounds = 0;
+
+ /*
+ * The following could happen in situations where rel has a pg_class
+ * entry but not the pg_partitioned_table entry yet.
+ */
+ if (key == NULL)
+ return;
+
+ /* Get partition oids from pg_inherits */
+ inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
+
+ /* Collect bound spec nodes in a list */
+ i = 0;
+ partoids = NIL;
+ foreach(cell, inhoids)
+ {
+ Oid inhrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *boundspec;
+
+ tuple = SearchSysCache1(RELOID, inhrelid);
+
+ /*
+ * It is possible that the pg_class tuple of a partition has not been
+ * updated yet to set its relpartbound field. The only case where
+ * this happens is when we open the parent relation to check using its
+ * partition descriptor that a new partition's bound does not overlap
+ * some existing partition.
+ */
+ if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
+ {
+ ReleaseSysCache(tuple);
+ continue;
+ }
+
+ datum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ boundspec = stringToNode(TextDatumGetCString(datum));
+ boundspecs = lappend(boundspecs, boundspec);
+ ReleaseSysCache(tuple);
+ partoids = lappend_oid(partoids, inhrelid);
+ }
+
+ nparts = list_length(partoids);
+
+ if (nparts > 0)
+ {
+ oids = (Oid *) palloc(nparts * sizeof(Oid));
+ i = 0;
+ foreach (cell, partoids)
+ oids[i++] = lfirst_oid(cell);
+
+ /* Convert from node to the internal representation */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ List *non_null_values = NIL;
+
+ /*
+ * Create a unified list of non-null values across all
+ * partitions.
+ */
+ i = 0;
+ found_null_partition = false;
+ null_partition_index = -1;
+ foreach(cell, boundspecs)
+ {
+ ListCell *c;
+ PartitionBoundSpec *spec = lfirst(cell);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ elog(ERROR, "invalid strategy in partition bound spec");
+
+ foreach (c, spec->listdatums)
+ {
+ Const *val = lfirst(c);
+ PartitionListValue *list_value = NULL;
+
+ if (!val->constisnull)
+ {
+ list_value = (PartitionListValue *)
+ palloc0(sizeof(PartitionListValue));
+ list_value->index = i;
+ list_value->value = val->constvalue;
+ }
+ else
+ {
+ /*
+ * Never put a null into the values array, flag
+ * instead for the code further down below where
+ * we construct the actual relcache struct.
+ */
+ if (found_null_partition)
+ elog(ERROR, "found null more than once");
+ found_null_partition = true;
+ null_partition_index = i;
+ }
+
+ if (list_value)
+ non_null_values = lappend(non_null_values,
+ list_value);
+ }
+
+ i++;
+ }
+
+ all_values_count = list_length(non_null_values);
+
+ /*
+ * Collect all list values in one array. Alongside the value,
+ * we also save the index of partition the value comes from.
+ */
+ all_values = (PartitionListValue **)
+ palloc(all_values_count *
+ sizeof(PartitionListValue *));
+ i = 0;
+ foreach(cell, non_null_values)
+ {
+ PartitionListValue *src = lfirst(cell);
+
+ all_values[i] = (PartitionListValue *)
+ palloc(sizeof(PartitionListValue));
+ all_values[i]->value = src->value;
+ all_values[i]->index = src->index;
+ i++;
+ }
+
+ qsort_arg(all_values, all_values_count,
+ sizeof(PartitionListValue *),
+ qsort_partition_list_value_cmp,
+ (void *) key);
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ int j, k;
+ PartitionRangeBound **all_bounds,
+ *prev;
+ bool *distinct_indexes;
+
+ all_bounds = (PartitionRangeBound **) palloc0(2 * nparts *
+ sizeof(PartitionRangeBound *));
+ distinct_indexes = (bool *) palloc(2 * nparts * sizeof(bool));
+
+ /*
+ * Create a unified list of range bounds across all the
+ * partitions.
+ */
+ i = j = 0;
+ foreach(cell, boundspecs)
+ {
+ PartitionBoundSpec *spec = lfirst(cell);
+ PartitionRangeBound *lower, *upper;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ elog(ERROR, "invalid strategy in partition bound spec");
+
+ lower = make_one_range_bound(key, i, spec->lowerdatums,
+ true);
+ upper = make_one_range_bound(key, i, spec->upperdatums,
+ false);
+ all_bounds[j] = lower;
+ all_bounds[j+1] = upper;
+ j += 2;
+ i++;
+ }
+ Assert(j == 2 * nparts);
+
+ /* Sort all the bounds in ascending order */
+ qsort_arg(all_bounds, 2 * nparts,
+ sizeof(PartitionRangeBound *),
+ qsort_partition_rbound_cmp,
+ (void *) key);
+ /*
+ * Count the number of distinct bounds to allocate an array
+ * of that size.
+ */
+ num_distinct_bounds = 0;
+ prev = NULL;
+ for (i = 0; i < 2 * nparts; i++)
+ {
+ PartitionRangeBound *cur = all_bounds[i];
+
+ /*
+ * Count the current bound if it is distinct from the
+ * previous one. Also, store if the index i contains
+ * a distinct bound that we'd like put in the relcache
+ * array.
+ */
+ if (prev == NULL || !partition_rbound_eq(key, cur, prev))
+ {
+ distinct_indexes[i] = true;
+ num_distinct_bounds++;
+ }
+ else
+ distinct_indexes[i] = false;
+
+ prev = cur;
+ }
+
+ /*
+ * Finally save them in an array from where they will be
+ * copied into the relcache.
+ */
+ distinct_bounds = (PartitionRangeBound **)
+ palloc0(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ k = 0;
+ for (i = 0; i < 2 * nparts; i++)
+ {
+ if (distinct_indexes[i])
+ distinct_bounds[k++] = all_bounds[i];
+ }
+ Assert(k == num_distinct_bounds);
+ break;
+ }
+ }
+ }
+
+ /* Now build the actual relcache partition descriptor */
+ rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(rel),
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt);
+
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+ result->nparts = nparts;
+ if (nparts > 0)
+ {
+ int *mapping;
+ int next_index = 0;
+
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+ result->boundinfo = (BoundCollectionData *)
+ palloc0(sizeof(BoundCollectionData));
+ result->boundinfo->strategy = key->strategy;
+
+ /* Initialize mapping array with invalid values */
+ mapping = (int *) palloc(sizeof(int) * nparts);
+ for (i = 0; i < nparts; i++)
+ mapping[i] = -1;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ PartitionListInfo listinfo;
+
+ listinfo.nvalues = all_values_count;
+ listinfo.has_null = found_null_partition;
+ listinfo.values = (Datum *)
+ palloc0(all_values_count * sizeof(Datum));
+ listinfo.indexes = (int *)
+ palloc0(all_values_count * sizeof(int));
+
+ /*
+ * Copy values. Indexes of individual values are mapped to
+ * canonical values so that they match for any two list
+ * partitioned tables with same number of partitions and same
+ * lists per partition. One way to canonicalize is to assign
+ * the index in all_values[] of the smallest value of each
+ * partition as the index of all of the partition's values.
+ */
+ for (i = 0; i < all_values_count; i++)
+ {
+ listinfo.values[i] = datumCopy(all_values[i]->value,
+ key->parttypbyval[0],
+ key->parttyplen[0]);
+
+ /* If the old index is has no mapping, assign one */
+ if (mapping[all_values[i]->index] == -1)
+ mapping[all_values[i]->index] = next_index++;
+
+ listinfo.indexes[i] = mapping[all_values[i]->index];
+ }
+
+ /*
+ * If null-accepting partition has no mapped index yet, assign
+ * one. This could happen if such partition accepts only null
+ * and hence not covered in the above loop which only handled
+ * non-null values.
+ */
+ if (found_null_partition)
+ {
+ Assert(null_partition_index >= 0);
+ if (mapping[null_partition_index] == -1)
+ mapping[null_partition_index] = next_index++;
+ }
+
+ /* All partition must now have a valid mapping */
+ Assert(next_index == nparts);
+
+ if (found_null_partition)
+ listinfo.null_index = mapping[null_partition_index];
+ else
+ listinfo.null_index = -1;
+
+ result->boundinfo->bounds.lists = listinfo;
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeInfo rangeinfo;
+
+ rangeinfo.bounds = (PartitionRangeBound **)
+ palloc(num_distinct_bounds *
+ sizeof(PartitionRangeBound *));
+ rangeinfo.indexes = (int *)
+ palloc((num_distinct_bounds + 1) *
+ sizeof(int));
+
+ for (i = 0; i < num_distinct_bounds; i++)
+ {
+ rangeinfo.bounds[i] =
+ copy_range_bound(key, distinct_bounds[i]);
+
+ /*
+ * There is no mapping for invalid indexes.
+ *
+ * Any lower bounds in the distinct_bounds array have
+ * invalid indexes assigned, because the values between
+ * the previous bound (if there is one) and this (lower)
+ * bound are not part of the range of any existing
+ * partition.
+ */
+ if (rangeinfo.bounds[i]->lower)
+ rangeinfo.indexes[i] = -1;
+ else
+ {
+ int orig_index = rangeinfo.bounds[i]->index;
+
+ /* If the old index is has no mapping, assign one */
+ if (mapping[orig_index] == -1)
+ mapping[orig_index] = next_index++;
+
+ rangeinfo.indexes[i] = mapping[orig_index];
+ }
+ }
+ rangeinfo.indexes[i] = -1;
+ rangeinfo.nbounds = num_distinct_bounds;
+
+ result->boundinfo->bounds.ranges = rangeinfo;
+ break;
+ }
+ }
+
+ /*
+ * Now assign OIDs from the original array into mapped indexes
+ * of the result array. Order of OIDs in the former is defined
+ * by the catalog scan that retrived them, whereas that in the
+ * latter is defined by canonicalized representation of the
+ * list values or the range bounds.
+ */
+ for (i = 0; i < nparts; i++)
+ result->oids[mapping[i]] = oids[i];
+ pfree(mapping);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ rel->rd_partdesc = result;
+}
+
+/*
+ * Are two partition bound collections logically equal?
+ *
+ * Used in the keep logic of relcache.c (ie, in RelationClearRelation()).
+ * This is also useful when b1 and b2 are bound collections of two separate
+ * relations, respectively, because BoundCollection is a canonical
+ * representation of partition bounds.
+ */
+bool
+partition_bounds_equal(PartitionKey key,
+ BoundCollection b1, BoundCollection b2)
+{
+ if (b1->strategy != b2->strategy)
+ return false;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ if (!equal_list_info(key,
+ &b1->bounds.lists, &b2->bounds.lists))
+ return false;
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ if (!equal_range_info(key,
+ &b1->bounds.ranges, &b2->bounds.ranges))
+ return false;
+ break;
+ }
+
+ return true;
+}
+
+/*
+ * check_new_partition_bound
+ *
+ * Checks if the new partition's bound overlaps any of the existing partitions
+ * of parent. Also performs additional checks as necessary per strategy.
+ */
+void
+check_new_partition_bound(char *relname, Oid parentId, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ ParseState *pstate = make_parsestate(NULL);
+ int with = -1;
+ bool overlap = false;
+
+ parent = heap_open(parentId, NoLock); /* already locked */
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+
+ if (pdesc->nparts > 0)
+ {
+ ListCell *cell;
+ bool new_has_null = false;
+ PartitionListInfo listinfo;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+ Assert(listinfo.nvalues > 0 || listinfo.has_null);
+
+ /* If the new partition's list contains null, set a flag. */
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ {
+ new_has_null = true;
+ break;
+ }
+ }
+
+ /*
+ * Now check against listinfo whether the new partition's
+ * values are not already taken by existing partitions.
+ */
+ if (new_has_null && listinfo.has_null)
+ {
+ with = listinfo.null_index;
+ overlap = true;
+ }
+ else
+ {
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+ int found = -1;
+
+ /* bsearch a new list's value in listinfo->values */
+ if (!val->constisnull)
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ val->constvalue);
+ if (found >= 0)
+ {
+ overlap = true;
+ with = listinfo.indexes[found];
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ PartitionRangeBound *lower,
+ *upper;
+
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ lower = make_one_range_bound(key, -1, spec->lowerdatums, true);
+ upper = make_one_range_bound(key, -1, spec->upperdatums, false);
+
+ /*
+ * First check if the resulting range would be empty with
+ * specified bounds
+ */
+ if (partition_rbound_cmp(key, lower, upper) >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create range partition with empty range"),
+ parser_errposition(pstate, spec->location)));
+
+ if (pdesc->nparts > 0)
+ {
+ PartitionRangeInfo rangeinfo;
+ int idx1, idx2;
+ bool equal = false;
+
+ Assert(pdesc->boundinfo &&
+ pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ /*
+ * Find the greatest index of a range bound that is less
+ * than the new lower bound.
+ */
+ idx1 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, lower,
+ partition_rbound_cmp, false,
+ &equal);
+
+ /*
+ * If equal has been set to true, that means the new bound is
+ * found to be equal to some existing bound. offset1 == -1
+ * means that all existing range bounds are greater than the
+ * new bound. If offset >= 0, and there exists a gap between
+ * the corresponding bound and the next, new partition can
+ * still manage to fit within the same. To confirm whether it
+ * is so, check the new upper bound.
+ */
+ if (!equal && (idx1 < 0 || rangeinfo.indexes[idx1+1] < 0))
+ {
+ idx2 = partition_rbound_bsearch(key, rangeinfo.bounds,
+ rangeinfo.nbounds, upper,
+ partition_rbound_cmp,
+ false, &equal);
+
+ if (equal || idx1 != idx2)
+ {
+ overlap = true;
+ with = rangeinfo.indexes[idx2+1];
+ }
+ }
+ else
+ {
+ overlap = true;
+ if (idx1 == -1)
+ {
+ Assert(equal);
+ idx1 = 0;
+ }
+ with = rangeinfo.indexes[idx1+1];
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (overlap)
+ {
+ Assert(with >= 0);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" would overlap partition \"%s\"",
+ relname, get_rel_name(pdesc->oids[with])),
+ parser_errposition(pstate, spec->location)));
+ }
+
+ heap_close(parent, NoLock);
+}
+
+/*
+ * get_partition_parent
+ *
+ * Returns inheritance parent of a partition by scanning pg_inherits
+ *
+ * Note: Because this function assumes that the relation whose OID is passed
+ * as an argument will have precisely one parent, it should only be called
+ * when it is known that the relation is a partition.
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+ Oid result;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+ result = form->inhparent;
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return result;
+}
+
+/*
+ * get_qual_from_partbound
+ * Given a parser node for partition bound, return the list of executable
+ * expressions as partition constraint
+ */
+List *
+get_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_qual = NIL;
+ TupleDesc parent_tupdesc = RelationGetDescr(parent);
+ AttrNumber parent_attno;
+ AttrNumber *partition_attnos;
+ bool found_whole_row;
+
+ Assert(key != NULL);
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->strategy == PARTITION_STRATEGY_LIST);
+ my_qual = get_qual_for_list(key, spec);
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->strategy == PARTITION_STRATEGY_RANGE);
+ my_qual = get_qual_for_range(key, spec);
+ break;
+ }
+
+ /*
+ * Translate vars in the generated expression to have correct attnos.
+ * Note that the vars in my_qual bear attnos dictated by key which carries
+ * physical attnos of the parent. We must allow for a case where physical
+ * attnos of a partition can be different from the parent.
+ */
+ partition_attnos = (AttrNumber *)
+ palloc0(parent_tupdesc->natts * sizeof(AttrNumber));
+ for (parent_attno = 1; parent_attno <= parent_tupdesc->natts;
+ parent_attno++)
+ {
+ Form_pg_attribute attribute = parent_tupdesc->attrs[parent_attno - 1];
+ char *attname = NameStr(attribute->attname);
+ AttrNumber partition_attno;
+
+ if (attribute->attisdropped)
+ continue;
+
+ partition_attno = get_attnum(RelationGetRelid(rel), attname);
+ partition_attnos[parent_attno - 1] = partition_attno;
+ }
+
+ my_qual = (List *) map_variable_attnos((Node *) my_qual,
+ 1, 0,
+ partition_attnos,
+ parent_tupdesc->natts,
+ &found_whole_row);
+ /* there can never be a whole-row reference in there*/
+ Assert(!found_whole_row);
+
+ return my_qual;
+}
+
+/*
+ * RelationGetPartitionQual
+ *
+ * Returns a list of partition quals
+ */
+List *
+RelationGetPartitionQual(Relation rel, bool recurse)
+{
+ /* Quick exit */
+ if (!rel->rd_rel->relispartition)
+ return NIL;
+
+ return generate_partition_qual(rel, recurse);
+}
+
+/* Module-local functions */
+
+/*
+ * get_qual_for_list
+ *
+ * Returns a list of expressions to use as a list partition's constraint.
+ */
+static List *
+get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result;
+ ArrayExpr *arr;
+ ScalarArrayOpExpr *opexpr;
+ ListCell *cell,
+ *prev,
+ *next;
+ Node *key_col;
+ Oid operoid;
+ bool need_relabel,
+ list_has_null = false;
+ NullTest *nulltest1 = NULL,
+ *nulltest2 = NULL;
+
+ /* Left operand is either a simple Var or arbitrary expression */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1,
+ key->partattrs[0],
+ key->parttypid[0],
+ key->parttypmod[0],
+ key->parttypcoll[0],
+ 0);
+ else
+ key_col = (Node *) copyObject(linitial(key->partexprs));
+
+ /*
+ * If the list does not accept nulls, we must add a IS NOT NULL test.
+ * If it does, remove the null Const from the list and create a IS NULL
+ * test and create an OR expression along with ScalarArrayOpExpr. We do
+ * it this way because the null-valued expression does not have the
+ * desired behavior when used within ScalarArrayOpExpr or OpExpr.
+ */
+ prev = NULL;
+ for (cell = list_head(spec->listdatums); cell; cell = next)
+ {
+ Const *val = (Const *) lfirst(cell);
+
+ next = lnext(cell);
+
+ if (val->constisnull)
+ {
+ list_has_null = true;
+ spec->listdatums = list_delete_cell(spec->listdatums,
+ cell, prev);
+ }
+ else
+ prev = cell;
+ }
+
+ if (!list_has_null)
+ {
+ /* Gin up a col IS NOT NULL test */
+ nulltest1 = makeNode(NullTest);
+ nulltest1->arg = (Expr *) key_col;
+ nulltest1->nulltesttype = IS_NOT_NULL;
+ nulltest1->argisrow = false;
+ nulltest1->location = -1;
+ }
+ else
+ {
+ /* Gin up a col IS NULL test */
+ nulltest2 = makeNode(NullTest);
+ nulltest2->arg = (Expr *) key_col;
+ nulltest2->nulltesttype = IS_NULL;
+ nulltest2->argisrow = false;
+ nulltest2->location = -1;
+ }
+
+ /* Right operand is an ArrayExpr */
+ arr = makeNode(ArrayExpr);
+ arr->array_typeid = !type_is_array(key->parttypid[0])
+ ? get_array_type(key->parttypid[0])
+ : key->parttypid[0];
+ arr->array_collid = key->parttypcoll[0];
+ arr->element_typeid = key->parttypid[0];
+ arr->elements = spec->listdatums;
+ arr->multidims = false;
+ arr->location = -1;
+
+ /* Get the correct btree equality operator */
+ operoid = get_partition_operator(key, 0, BTEqualStrategyNumber,
+ &need_relabel);
+ if (need_relabel || key->partcollation[0] != key->parttypcoll[0])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[0],
+ -1,
+ key->partcollation[0],
+ COERCE_EXPLICIT_CAST);
+
+ /* Build leftop = ANY (rightop) */
+ opexpr = makeNode(ScalarArrayOpExpr);
+ opexpr->opno = operoid;
+ opexpr->opfuncid = get_opcode(operoid);
+ opexpr->useOr = true;
+ opexpr->inputcollid = key->partcollation[0];
+ opexpr->args = list_make2(key_col, arr);
+ opexpr->location = -1;
+
+ if (nulltest1)
+ result = list_make2(nulltest1, opexpr);
+ else if (nulltest2)
+ {
+ Expr *or;
+
+ or = makeBoolExpr(OR_EXPR, list_make2(nulltest2, opexpr), -1);
+ result = list_make1(or);
+ }
+ else
+ result = list_make1(opexpr);
+
+ return result;
+}
+
+/*
+ * get_qual_for_range
+ *
+ * Get a list of OpExpr's to use as a range partition's constraint.
+ *
+ * For each column, we also emit a IS NOT NULL test since we do not
+ * allow null values in range partition key.
+ */
+static List *
+get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
+{
+ List *result = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+
+ /*
+ * Iterate over columns of the key, emitting an OpExpr for each using
+ * the corresponding lower and upper datums as constant operands.
+ */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum = lfirst(cell1),
+ *udatum = lfirst(cell2);
+ Node *key_col;
+ Const *lower_val = NULL,
+ *upper_val = NULL;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+ bool need_relabel = false;
+ NullTest *nulltest;
+ Oid operoid;
+
+ /* Left operand */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1,
+ key->partattrs[i],
+ key->parttypid[i],
+ key->parttypmod[i],
+ key->parttypcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) copyObject(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /* Gin up a col IS NOT NULL test */
+ nulltest = makeNode(NullTest);
+ nulltest->arg = (Expr *) key_col;
+ nulltest->nulltesttype = IS_NOT_NULL;
+ nulltest->argisrow = false;
+ nulltest->location = -1;
+ result = lappend(result, nulltest);
+
+ /*
+ * Stop at this column if either of lower or upper datum is infinite,
+ * but do emit an OpExpr for the non-infinite datum.
+ */
+ if (!ldatum->infinite)
+ lower_val = (Const *) ldatum->value;
+ if (!udatum->infinite)
+ upper_val = (Const *) udatum->value;
+
+ /*
+ * If lower_val and upper_val are both finite and happen to be equal,
+ * emit only (key_col = lower_val) for this column, because all rows
+ * in this partition could only ever contain this value (ie, lower_val)
+ * in the current partitioning column. We must consider further
+ * columns because the above condition does not fully constrain the
+ * rows of this partition.
+ */
+ if (lower_val && upper_val)
+ {
+ /* Get the correct btree equality operator for the test */
+ operoid = get_partition_operator(key, i, BTEqualStrategyNumber,
+ &need_relabel);
+
+ /* Create the test expression */
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ /* This can never be, but it's better to make sure */
+ if (i == key->partnatts - 1)
+ elog(ERROR, "invalid range bound specification");
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+
+ /* Go over to consider the next column. */
+ i++;
+ continue;
+ }
+ }
+
+ /*
+ * We can say here that lower_val <> upper_val. Emit expressions
+ * (key_col >= lower_val) and (key_col < upper_val), then stop.
+ */
+ if (lower_val)
+ {
+ operoid = get_partition_operator(key, i,
+ BTGreaterEqualStrategyNumber,
+ &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ if (upper_val)
+ {
+ operoid = get_partition_operator(key, i,
+ BTLessStrategyNumber,
+ &need_relabel);
+
+ if (need_relabel || key->partcollation[i] != key->parttypcoll[i])
+ key_col = (Node *) makeRelabelType((Expr *) key_col,
+ key->partopcintype[i],
+ -1,
+ key->partcollation[i],
+ COERCE_EXPLICIT_CAST);
+
+ result = lappend(result,
+ make_opclause(operoid,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->partcollation[i]));
+ }
+
+ /*
+ * We can stop at this column, because we would not have checked
+ * the next column when routing a given row into this partition.
+ */
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_operator
+ *
+ * Return oid of the operator of given strategy for a given partition key
+ * column.
+ */
+static Oid
+get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
+ bool *need_relabel)
+{
+ Oid operoid;
+
+ /*
+ * First check if there exists an operator of the given strategy, with
+ * this column's type as both its lefttype and righttype, in the
+ * partitioning operator family specified for the column.
+ */
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->parttypid[col],
+ key->parttypid[col],
+ strategy);
+
+ /*
+ * If one doesn't exist, we must resort to using an operator in the same
+ * opreator family but with the operator class declared input type. It is
+ * OK to do so, because the column's type is known to be binary-coercible
+ * with the operator class input type (otherwise, the operator class in
+ * question would not have been accepted as the partitioning operator
+ * class). We must however inform the caller to wrap the non-Const
+ * expression with a RelabelType node to denote the implicit coercion. It
+ * ensures that the resulting expression structurally matches similarly
+ * processed expressions within the optimizer.
+ */
+ if (!OidIsValid(operoid))
+ {
+ operoid = get_opfamily_member(key->partopfamily[col],
+ key->partopcintype[col],
+ key->partopcintype[col],
+ strategy);
+ *need_relabel = true;
+ }
+ else
+ *need_relabel = false;
+
+ if (!OidIsValid(operoid))
+ elog(ERROR, "could not find operator for partitioning");
+
+ return operoid;
+}
+
+/*
+ * generate_partition_qual
+ *
+ * Generate partition predicate from rel's partition bound expression
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * through, we build in working memory and then copy the completed structure
+ * into cache memory.
+ */
+static List *
+generate_partition_qual(Relation rel, bool recurse)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_qual = NIL,
+ *result = NIL;
+ Relation parent;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* Grab at least an AccessShareLock on the parent table */
+ parent = heap_open(get_partition_parent(RelationGetRelid(rel)),
+ AccessShareLock);
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ {
+ if (parent->rd_rel->relispartition && recurse)
+ result = list_concat(generate_partition_qual(parent, true),
+ copyObject(rel->rd_partcheck));
+ else
+ result = copyObject(rel->rd_partcheck);
+
+ heap_close(parent, AccessShareLock);
+ return result;
+ }
+
+ /* Get pg_class.relpartbound */
+ Assert(rel->rd_rel->relispartition);
+ tuple = SearchSysCache1(RELOID, RelationGetRelid(rel));
+ boundDatum = SysCacheGetAttr(RELOID, tuple,
+ Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+ ReleaseSysCache(tuple);
+
+ my_qual = get_qual_from_partbound(rel, parent, bound);
+
+ /* If requested, add parent's quals to the list (if any) */
+ if (parent->rd_rel->relispartition && recurse)
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_qual(parent, true);
+ result = list_concat(parent_check, my_qual);
+ }
+ else
+ result = my_qual;
+
+ /* Save a copy of my_qual in the relcache */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(my_qual);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Keep the parent locked until commit */
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/* List partition related support functions */
+
+/*
+ * Return whether two list partition bound collections are logically equal
+ */
+static bool
+equal_list_info(PartitionKey key,
+ PartitionListInfo *l1, PartitionListInfo *l2)
+{
+ int i;
+
+ if (l1->nvalues != l2->nvalues)
+ return false;
+
+ if (l1->has_null != l2->has_null)
+ return false;
+
+ for (i = 0; i < l1->nvalues; i++)
+ {
+ if (partition_list_values_cmp(key, l1->values[i], l2->values[i]))
+ return false;
+
+ if(l1->indexes[i] != l2->indexes[i])
+ return false;
+ }
+
+ if (l1->null_index != l2->null_index)
+ return false;
+
+ return true;
+}
+
+/* Compare two list value datums */
+static int32
+partition_list_values_cmp(PartitionKey key, Datum val1, Datum val2)
+{
+ return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->partcollation[0],
+ val1, val2));
+}
+
+/*
+ * qsort_partition_list_value_cmp
+ *
+ * Compare two list values
+ */
+static int32
+qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
+{
+ Datum value1 = (*(const PartitionListValue **) a)->value,
+ value2 = (*(const PartitionListValue **) b)->value;
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_list_values_cmp(key, value1, value2);
+}
+
+/* Binary search for list partition values; returns -1 if not found */
+static int
+partition_list_values_bsearch(PartitionKey key, const Datum *values, int n,
+ const Datum probe)
+{
+ int lo,
+ hi;
+
+ lo = 0;
+ hi = n - 1;
+ while (lo <= hi)
+ {
+ int mid = (lo + hi) / 2;
+ int32 res = partition_list_values_cmp(key, probe, values[mid]);
+
+ if (res < 0)
+ hi = mid - 1;
+ else if (res > 0)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* Range partition related support functions */
+
+/*
+ * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
+ * and a flag telling whether the bound is lower or not.
+ */
+static PartitionRangeBound *
+make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
+{
+ PartitionRangeBound *bound;
+ ListCell *cell;
+ int i;
+
+ bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ bound->index = index;
+ bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ bound->infinite = (bool *) palloc0(key->partnatts * sizeof(bool));
+ bound->lower = lower;
+
+ i = 0;
+ foreach (cell, datums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+
+ bound->infinite[i] = datum->infinite;
+
+ if (!bound->infinite[i])
+ {
+ Const *val = (Const *) datum->value;
+
+ if (val->constisnull)
+ elog(ERROR, "invalid range bound datum");
+ bound->datums[i] = val->constvalue;
+ }
+
+ i++;
+ }
+
+ return bound;
+}
+
+/*
+ * Return a copy of input PartitionRangeBound
+ */
+static PartitionRangeBound *
+copy_range_bound(PartitionKey key, PartitionRangeBound *src)
+{
+ int i;
+ int partnatts = key->partnatts;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->index = src->index;
+ result->datums = (Datum *) palloc0(partnatts * sizeof(Datum));
+ result->infinite = (bool *) palloc0(partnatts * sizeof(bool));
+ result->lower = src->lower;
+
+ for (i = 0; i < partnatts; i++)
+ {
+ result->infinite[i] = src->infinite[i];
+ if (!result->infinite[i])
+ result->datums[i] = datumCopy(src->datums[i],
+ key->parttypbyval[i],
+ key->parttyplen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Return whether two range partition bound collections are logically equal
+ */
+static bool
+equal_range_info(PartitionKey key,
+ PartitionRangeInfo *r1, PartitionRangeInfo *r2)
+{
+ int i;
+
+ if (r1->nbounds != r2->nbounds)
+ return false;
+
+ for (i = 0; i < r1->nbounds; i++)
+ {
+ if (partition_rbound_cmp(key, r1->bounds[i], r2->bounds[i]) != 0)
+ return false;
+
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+ }
+
+ /* There is one more index than bounds */
+ if (r1->indexes[i] != r2->indexes[i])
+ return false;
+
+ return true;
+}
+
+/*
+ * Return for two range bounds whether the 1st one is <=, =, >= the 2nd
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
+{
+ PartitionRangeBound *b2 = (PartitionRangeBound *) arg;
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int32 cmpval;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ /*
+ * First, handle cases involving infinity, which don't require
+ * invoking the comparison proc.
+ */
+ if (b1->infinite[i] && b2->infinite[i])
+ {
+ /*
+ * 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[i])
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite[i])
+ return b2->lower ? 1 : -1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /*
+ * 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. Remember lower bounds are inclusive.
+ */
+ if (cmpval == 0)
+ {
+ /*
+ * If both are either inclusive or exclusive, they are trivially
+ * equal
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ /* Exclusive one is smaller of the two */
+ else
+ return b1->lower ? 1 : -1;
+ }
+
+ return cmpval;
+}
+
+/*
+ * Return whether two range bounds are equal simply by comparing datums
+ */
+static bool
+partition_rbound_eq(PartitionKey key,
+ PartitionRangeBound *b1, PartitionRangeBound *b2)
+{
+ Datum *datums1 = b1->datums,
+ *datums2 = b2->datums;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ int32 cmpval = 0;
+
+ /*
+ * If either of them has infinite element, we can't equate them. Even
+ * if both were infinite, they'd have opposite signs (it's always true
+ * that b1 and b2 are different types of bounds).
+ */
+ if (b1->infinite[i] || b2->infinite[i])
+ return false;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ return false;
+ }
+
+ return true;
+}
+
+/* Used when sorting range bounds across all range partitions */
+static int32
+qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(PartitionRangeBound *const *) a);
+ PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
+ PartitionKey key = (PartitionKey) arg;
+
+ return partition_rbound_cmp(key, b1, b2);
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less (less or equal) than given range bound. If all
+ * range bounds in array are greater or equal (greater) than given range
+ * bound, return -1.
+ *
+ * cmp is a custom comparison function basically concerned with whether probe
+ * is a partition bound (PartitionRangeBound) or a simple Datum array
+ * containing partition key of a tuple. The first argument is always a
+ * range bound.
+ *
+ * *is_equal returns whether the concluding comparison returned equal.
+ */
+static int
+partition_rbound_bsearch(PartitionKey key, PartitionRangeBound **bounds, int n,
+ void *probe, partition_rbound_bsearch_cmp_fn cmp,
+ bool equal_allowed, bool *is_equal)
+{
+ int lo, hi, mid;
+
+ lo = -1;
+ hi = n - 1;
+ while (lo < hi)
+ {
+ PartitionRangeBound *cur;
+ int32 cmpval;
+
+ mid = (lo + hi + 1) / 2;
+ cur = bounds[mid];
+
+ cmpval = cmp(key, cur, probe);
+ if (is_equal)
+ *is_equal = (cmpval == 0);
+
+ if (cmpval < 0 || (equal_allowed && cmpval == 0))
+ lo = mid;
+ else
+ hi = mid - 1;
+ }
+
+ return lo;
+}
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5b4f6af..d6d52d9 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
* Create the relation. (This will error out if there's an existing view,
* so we don't need more code to complain if "replace" is false.)
*/
- intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
+ intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
/*
* If necessary, create a TOAST table for the target table. Note that
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e08fd5d..d4a1f01 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists;
- address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
+ address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
seqoid = address.objectId;
Assert(seqoid != InvalidOid);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1ddf443..3b72ae3 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"
@@ -65,6 +66,8 @@
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "optimizer/predtest.h"
+#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
@@ -163,6 +166,7 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ List *partition_constraint; /* for attach partition validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -279,7 +283,8 @@ struct DropRelationCallbackState
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ bool is_partition, List **supOids, List **supconstr,
+ int *supOidCount);
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);
@@ -346,7 +351,9 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
@@ -444,6 +451,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_exp
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy);
static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
List **partexprs, Oid *partopclass, Oid *partcollation);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -466,7 +478,7 @@ static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *pa
*/
ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress)
+ ObjectAddress *typaddress, const char *queryString)
{
char relname[NAMEDATALEN];
Oid namespaceId;
@@ -597,6 +609,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
+ stmt->partbound != NULL,
&inheritOids, &old_constraints, &parentOidCount);
/*
@@ -607,18 +620,33 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
descriptor = BuildDescForRelation(schema);
/*
- * Notice that we allow OIDs here only for plain tables, even though some
- * other relkinds can support them. This is necessary because the
- * default_with_oids GUC must apply only to plain tables and not any other
- * relkind; doing otherwise would break existing pg_dump files. We could
- * allow explicit "WITH OIDS" while not allowing default_with_oids to
- * affect other relkinds, but it would complicate interpretOidsOption().
+ * Notice that we allow OIDs here only for plain tables and partitioned
+ * tables, even though some other relkinds can support them. This is
+ * necessary because the default_with_oids GUC must apply only to plain
+ * tables and not any other relkind; doing otherwise would break existing
+ * pg_dump files. We could allow explicit "WITH OIDS" while not allowing
+ * default_with_oids to affect other relkinds, but it would complicate
+ * interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
(relkind == RELKIND_RELATION ||
relkind == RELKIND_PARTITIONED_TABLE));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
+ if (stmt->partbound)
+ {
+ /* If the parent has OIDs, partitions must have them too. */
+ if (parentOidCount > 0 && !localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table without OIDs as partition of table with OIDs")));
+ /* If the parent doesn't, partitions must not have them. */
+ if (parentOidCount == 0 && localHasOids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+ }
+
/*
* Find columns with default values and prepare for insertion of the
* defaults. Pre-cooked (that is, inherited) defaults go into a list of
@@ -717,6 +745,51 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
*/
rel = relation_open(relationId, AccessExclusiveLock);
+ /* Process and store partition bound, if any. */
+ if (stmt->partbound)
+ {
+ Node *bound;
+ ParseState *pstate;
+ Oid parentId = linitial_oid(inheritOids);
+ Relation parentRel;
+
+ /* Already have strong enough lock on the parent */
+ parentRel = heap_open(parentId, NoLock);
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of parentRel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* Tranform the bound values */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+ bound = transformPartitionBound(pstate, parentRel, stmt->partbound);
+ heap_close(parentRel, NoLock);
+
+ /*
+ * Check first that the new partition's bound is valid and does not
+ * overlap with any of existing partitions of the parent - note that
+ * it does not return on error.
+ */
+ check_new_partition_bound(relname, parentId, bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(rel, bound);
+
+ /*
+ * The code that follows may also update the pg_class tuple to update
+ * relnumchecks, so bump up the command counter to avoid the "already
+ * updated by self" error.
+ */
+ CommandCounterIncrement();
+ }
+
/*
* Process the partitioning specification (if any) and store the
* partition key information into the catalog.
@@ -1117,6 +1190,10 @@ ExecuteTruncate(TruncateStmt *stmt)
relids = lappend_oid(relids, childrelid);
}
}
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("must truncate child tables too")));
}
/*
@@ -1423,6 +1500,7 @@ storage_name(char c)
* of ColumnDef's.) It is destructively changed.
* 'supers' is a list of names (as RangeVar nodes) of parent relations.
* 'relpersistence' is a persistence type of the table.
+ * 'is_partition' tells if the table is a partition
*
* Output arguments:
* 'supOids' receives a list of the OIDs of the parent relations.
@@ -1474,7 +1552,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ bool is_partition, List **supOids, List **supconstr,
+ int *supOidCount)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1484,6 +1563,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
+ List *saved_schema = NIL;
/*
* Check for and reject tables with too many columns. We perform this
@@ -1503,6 +1583,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
MaxHeapAttributeNumber)));
/*
+ * In case of a partition, there are no new column definitions, only
+ * column options specified using the WITH OPTIONS clauses. We merge
+ * those options with actual column definitions after we have finished
+ * generating them from the parent's schema.
+ */
+ if (is_partition)
+ {
+ saved_schema = schema;
+ schema = NIL;
+ }
+
+ /*
* Check for duplicate names in the explicit list of attributes.
*
* Although we might consider merging such entries in the same way that we
@@ -1582,18 +1674,35 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* on the parent table, which might otherwise be attempting to clear
* the parent's relhassubclass field, if its previous children were
* recently dropped.
+ *
+ * If the child table is a partition, then we instead grab an exclusive
+ * lock on the parent because its partition descriptor will be changed
+ * by addition of the new partition.
*/
- relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ if (!is_partition)
+ relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ else
+ relation = heap_openrv(parent, AccessExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ /*
+ * We do not allow partitioned tables and partitions to participate
+ * in regular inheritance.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
parent->relname)));
+ if (relation->rd_rel->relispartition && !is_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -1603,7 +1712,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation \"%s\"",
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation \"%s\""
+ : "cannot create as partition of temporary relation \"%s\"",
parent->relname)));
/* If existing rel is temp, it must belong to this session */
@@ -1611,7 +1722,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
!relation->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot inherit from temporary relation of another session")));
+ errmsg(!is_partition
+ ? "cannot inherit from temporary relation of another session"
+ : "cannot create as partition of temporary relation of another session")));
/*
* We should have an UNDER permission flag for this, but for now,
@@ -1858,7 +1971,8 @@ 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.
+ * columns into the inherited schema list. Although, we never have any
+ * explicitly declared columns if the table is a partition.
*/
if (inhSchema != NIL)
{
@@ -1887,6 +2001,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
newcollid;
/*
+ * Partitions have only one parent, so conflict should never
+ * occur
+ */
+ Assert(!is_partition);
+
+ /*
* Yes, try to merge the two column definitions. They must
* have the same type, typmod, and collation.
*/
@@ -1968,6 +2088,56 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
/*
+ * Now that we have the column definition list for a partition, we can
+ * check whether the columns referenced in column option specifications
+ * actually exist. Also, we merge the options into the corresponding
+ * column definitions.
+ */
+ if (is_partition && list_length(saved_schema) > 0)
+ {
+ schema = list_concat(schema, saved_schema);
+
+ foreach(entry, schema)
+ {
+ ColumnDef *coldef = lfirst(entry);
+ ListCell *rest = lnext(entry);
+ ListCell *prev = entry;
+
+ /*
+ * Partition column option that does not belong to a column from
+ * the parent. This works because the columns from the parent
+ * come first in the list (see above).
+ */
+ if (coldef->typeName == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist",
+ coldef->colname)));
+ while (rest != NULL)
+ {
+ ColumnDef *restdef = lfirst(rest);
+ ListCell *next = lnext(rest); /* need to save it in case
+ * we delete it */
+
+ if (strcmp(coldef->colname, restdef->colname) == 0)
+ {
+ /*
+ * 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;
+ list_delete_cell(schema, rest, prev);
+ }
+ prev = rest;
+ rest = next;
+ }
+ }
+ }
+
+ /*
* If we found any conflicting parent default values, check to make sure
* they were overridden by the child.
*/
@@ -3129,6 +3299,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);
@@ -3240,12 +3415,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepDropNotNull(rel, recurse, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ ATPrepSetNotNull(rel, recurse, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
@@ -3446,6 +3623,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);
@@ -3516,7 +3699,14 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- if (tab->relkind == RELKIND_RELATION ||
+ /*
+ * If the table is source table of ATTACH PARTITION command, we did
+ * not modify anything about it that will change its toasting
+ * requirement, so no need to check.
+ */
+ if (((tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_TABLE) &&
+ tab->partition_constraint == NIL) ||
tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
@@ -3765,6 +3955,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);
@@ -3950,7 +4146,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull ||
+ tab->partition_constraint != NIL)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4030,6 +4227,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
CommandId mycid;
BulkInsertState bistate;
int hi_options;
+ List *partqualstate = NIL;
/*
* Open the relation(s). We have surely already locked the existing
@@ -4094,6 +4292,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /* Build expression execution states for partition check quals */
+ if (tab->partition_constraint)
+ {
+ needscan = true;
+ partqualstate = (List *)
+ ExecPrepareExpr((Expr *) tab->partition_constraint,
+ estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4283,6 +4490,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("partition constraint is violated by some row")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4480,7 +4692,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4802,6 +5015,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5248,6 +5466,20 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be dropped from child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !recurse && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+}
static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
@@ -5323,6 +5555,23 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
+ /* If rel is partition, shouldn't drop NOT NULL if parent has the same */
+ if (rel->rd_rel->relispartition)
+ {
+ Oid parentId = get_partition_parent(RelationGetRelid(rel));
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is marked NOT NULL in parent table",
+ colName)));
+ heap_close(parent, AccessShareLock);
+ }
+
/*
* Okay, actually perform the catalog change ... if needed
*/
@@ -5355,6 +5604,21 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
* Return the address of the modified column. If the column was already NOT
* NULL, InvalidObjectAddress is returned.
*/
+
+static void
+ATPrepSetNotNull(Relation rel, bool recurse, bool recursing)
+{
+ /*
+ * If the parent is a partitioned table, like check constraints, NOT NULL
+ * constraints must be added to the child tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+ !recurse && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be added to child tables too")));
+}
+
static ObjectAddress
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
@@ -5914,6 +6178,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
Relation attr_rel;
ListCell *child;
+ /*
+ * In case of a partitioned table, the column must be dropped from the
+ * partitions as well.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column must be dropped from child tables too")));
+
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(child, children)
{
@@ -7916,6 +8189,16 @@ ATExecDropConstraint(Relation rel, const char *constrName,
}
/*
+ * In case of a partitioned table, the constraint must be dropped from
+ * the partitions too. There is no such thing as NO INHERIT constraints
+ * in case of partitioned tables.
+ */
+ if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be dropped from child tables too")));
+
+ /*
* 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.
@@ -10217,6 +10500,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10229,12 +10517,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;
@@ -10279,37 +10562,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
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);
+ /* Likewise for partitions */
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
/*
* Prevent circularity by seeing if proposed parent inherits from child.
@@ -10344,6 +10601,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);
@@ -10358,16 +10678,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;
}
/*
@@ -10418,7 +10730,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
@@ -10436,12 +10748,17 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool child_is_partition = false;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ /* If parent_rel is a partitioned table, child_rel must be a partition */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ child_is_partition = true;
+
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
@@ -10489,6 +10806,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
* later on, this change will just roll back.)
*/
childatt->attinhcount++;
+
+ /*
+ * In case of partitions, we must enforce that value of attislocal
+ * is same in all partitions. (Note: there are only inherited
+ * attributes in partitions)
+ */
+ if (child_is_partition)
+ {
+ Assert(childatt->attinhcount == 1);
+ childatt->attislocal = false;
+ }
+
simple_heap_update(attrrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrrel, tuple);
heap_freetuple(tuple);
@@ -10511,7 +10840,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.
@@ -10530,10 +10859,15 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool child_is_partition = false;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ /* If parent_rel is a partitioned table, child_rel must be a partition */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ child_is_partition = true;
+
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
Anum_pg_constraint_conrelid,
@@ -10610,6 +10944,18 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
child_copy = heap_copytuple(child_tuple);
child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
child_con->coninhcount++;
+
+ /*
+ * In case of partitions, an inherited constraint must be
+ * inherited only once since it cannot have multiple parents and
+ * it is never considered local.
+ */
+ if (child_is_partition)
+ {
+ Assert(child_con->coninhcount == 1);
+ child_con->conislocal = false;
+ }
+
simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
CatalogUpdateIndexes(catalog_relation, child_copy);
heap_freetuple(child_copy);
@@ -10634,6 +10980,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10647,13 +11033,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];
@@ -10662,19 +11046,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool child_is_partition = false;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ /* If parent_rel is a partitioned table, child_rel must be a partition */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ child_is_partition = true;
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10684,7 +11060,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);
@@ -10705,11 +11081,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 (child_is_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
@@ -10718,7 +11103,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)))
@@ -10780,7 +11165,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);
@@ -10811,7 +11196,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)
@@ -10823,30 +11208,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;
}
/*
@@ -12531,3 +12906,447 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
attn++;
}
}
+
+/*
+ * 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)
+{
+ PartitionKey key = RelationGetPartitionKey(rel);
+ Relation attachRel,
+ catalog;
+ List *childrels;
+ TupleConstr *attachRel_constr;
+ List *partConstraint,
+ *existConstraint;
+ SysScanDesc scan;
+ ScanKeyData skey;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ bool skip_validate = false;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessExclusiveLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE | ATT_FOREIGN_TABLE);
+
+ /* A partition can only have one parent */
+ if (attachRel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /*
+ * attachRel should not already be part of inheritance; either as a child
+ * table...
+ */
+ catalog = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
+ NULL, 1, &skey);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach inheritance child as partition")));
+ systable_endscan(scan);
+
+ /* ...or be a RELKIND_RELATION parent table */
+ ScanKeyInit(&skey,
+ Anum_pg_inherits_inhparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
+ 1, &skey);
+ if (HeapTupleIsValid(systable_getnext(scan)) &&
+ attachRel->rd_rel->relkind == RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach inheritance parent as partition")));
+ systable_endscan(scan);
+ heap_close(catalog, AccessShareLock);
+
+ /*
+ * Prevent circularity by seeing if rel is a partition of attachRel.
+ * (In particular, this disallows making a rel a partition of itself.)
+ */
+ childrels = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessShareLock, NULL);
+ if (list_member_oid(childrels, RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("circular inheritance not allowed"),
+ errdetail("\"%s\" is already a child of \"%s\".",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(attachRel))));
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("New partition should contain only the columns present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ /*
+ * Check that the new partition's bound is valid and does not overlap any
+ * of existing partitions of the parent - note that it does not return
+ * on error.
+ */
+ check_new_partition_bound(RelationGetRelationName(attachRel),
+ RelationGetRelid(rel),
+ cmd->bound);
+
+ /* Update the pg_class entry. */
+ StorePartitionBound(attachRel, cmd->bound);
+
+ /*
+ * Generate partition constraint from the partition bound specification.
+ * If the parent itself is a partition, make sure to include its
+ * constraint as well.
+ */
+ partConstraint = list_concat(get_qual_from_partbound(attachRel, rel,
+ cmd->bound),
+ RelationGetPartitionQual(rel, true));
+ partConstraint = (List *) eval_const_expressions(NULL,
+ (Node *) partConstraint);
+ partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+ partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+ /*
+ * Check if we can do away with having to scan the table being attached
+ * to validate the partition constraint, by *proving* that the existing
+ * constraints of the table *imply* the partition predicate. We include
+ * the table's check constraints and NOT NULL constraints in the list of
+ * clauses passed to predicate_implied_by().
+ *
+ * There are some cases in which we cannot rely on just the result of
+ * the proof.
+ */
+ tupleDesc = RelationGetDescr(attachRel);
+ attachRel_constr = tupleDesc->constr;
+ existConstraint = NIL;
+ if (attachRel_constr > 0)
+ {
+ int num_check = attachRel_constr->num_check;
+ int i;
+ Bitmapset *not_null_attrs = NULL;
+
+ if (attachRel_constr->has_not_null)
+ {
+ int natts = attachRel->rd_att->natts;
+
+ for (i = 1; i <= natts; i++)
+ {
+ Form_pg_attribute att = attachRel->rd_att->attrs[i - 1];
+
+ if (att->attnotnull && !att->attisdropped)
+ {
+ NullTest *ntest = makeNode(NullTest);
+
+ ntest->arg = (Expr *) makeVar(1,
+ i,
+ att->atttypid,
+ att->atttypmod,
+ att->attcollation,
+ 0);
+ ntest->nulltesttype = IS_NOT_NULL;
+
+ /*
+ * argisrow=false is correct even for a composite column,
+ * because attnotnull does not represent a SQL-spec IS NOT
+ * NULL test in such a case, just IS DISTINCT FROM NULL.
+ */
+ ntest->argisrow = false;
+ ntest->location = -1;
+ existConstraint = lappend(existConstraint, ntest);
+ not_null_attrs = bms_add_member(not_null_attrs, i);
+ }
+ }
+ }
+
+ for (i = 0; i < num_check; i++)
+ {
+ Node *cexpr;
+
+ /*
+ * If this constraint hasn't been fully validated yet, we must
+ * ignore it here.
+ */
+ if (!attachRel_constr->check[i].ccvalid)
+ continue;
+
+ cexpr = stringToNode(attachRel_constr->check[i].ccbin);
+
+ /*
+ * Run each expression through const-simplification and
+ * canonicalization. It is necessary, because we will be
+ * comparing it to similarly-processed qual clauses, and may fail
+ * to detect valid matches without this.
+ */
+ cexpr = eval_const_expressions(NULL, cexpr);
+ cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
+
+ existConstraint = list_concat(existConstraint,
+ make_ands_implicit((Expr *) cexpr));
+ }
+
+ existConstraint = list_make1(make_ands_explicit(existConstraint));
+
+ /* And away we go ... */
+ if (predicate_implied_by(partConstraint, existConstraint))
+ skip_validate = true;
+
+ /*
+ * We choose to err on the safer side in certain cases, ie, give up on
+ * skipping the validation scan, if the partition key columns don't
+ * have the NOT NULL constraint. There are two such cases: a) if the
+ * table is to be a range partition, b) if the table is to be a list
+ * partition that does not accept nulls. In such cases, the partition
+ * predicate (partConstraint) does include an IS NOT NULL expression,
+ * however, because of the way predicate_implied_by_simple_clause()
+ * is designed to handle the IS NOT NULL predicates in the absence of
+ * a IS NOT NULL clause, we cannot rely on there being no NULL values
+ * in partition key column(s) in the rows of the table based only on
+ * the above proof.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRATEGY_RANGE:
+ for (i = 0; i < key->partnatts; i++)
+ {
+ if (!bms_is_member(get_partition_col_attnum(key, i),
+ not_null_attrs))
+ {
+ skip_validate = false;
+ break;
+ }
+ }
+ break;
+
+ case PARTITION_STRATEGY_LIST:
+ {
+ List *part_constr;
+ ListCell *lc;
+ bool partition_accepts_null = true;
+
+ /*
+ * Partition does not accept nulls if there is a IS NOT NULL
+ * expression in the partition constraint.
+ */
+ part_constr = linitial(partConstraint);
+ part_constr = make_ands_implicit((Expr *) part_constr);
+ foreach(lc, part_constr)
+ {
+ Node *expr = lfirst(lc);
+
+ if (IsA(expr, NullTest) &&
+ ((NullTest *) expr)->nulltesttype == IS_NOT_NULL)
+ {
+ partition_accepts_null = false;
+ break;
+ }
+ }
+
+ if (!partition_accepts_null &&
+ !bms_is_member(get_partition_col_attnum(key, 0),
+ not_null_attrs))
+ {
+ skip_validate = false;
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ if (skip_validate)
+ elog(NOTICE, "skipping scan to validate partition constraint");
+
+ /*
+ * Set up to have the table to be scanned to validate the partition
+ * constraint (see partConstraint above). If it's a partitioned table,
+ * we instead schdule its leaf partitions to be scanned instead.
+ */
+ if (!skip_validate)
+ {
+ List *all_parts;
+ ListCell *lc;
+
+ /* Take an exclusive lock on the partitions to be checked */
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ all_parts = find_all_inheritors(RelationGetRelid(attachRel),
+ AccessExclusiveLock, NULL);
+ else
+ all_parts = list_make1_oid(RelationGetRelid(attachRel));
+
+ foreach(lc, all_parts)
+ {
+ AlteredTableInfo *tab;
+ Oid part_relid = lfirst_oid(lc);
+ Relation part_rel;
+ Expr *constr;
+
+ /* Lock already taken */
+ if (part_relid != RelationGetRelid(attachRel))
+ part_rel = heap_open(part_relid, NoLock);
+ else
+ part_rel = attachRel;
+
+ /*
+ * Skip if it's a partitioned table. Only RELKIND_RELATION
+ * relations (ie, leaf partitions) need to be scanned.
+ */
+ if (part_rel != attachRel &&
+ part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(part_rel, NoLock);
+ continue;
+ }
+
+ /* Grab a work queue entry */
+ tab = ATGetQueueEntry(wqueue, part_rel);
+
+ constr = linitial(partConstraint);
+ tab->partition_constraint = make_ands_implicit((Expr *) constr);
+
+ /* keep our lock until commit */
+ if (part_rel != attachRel)
+ heap_close(part_rel, NoLock);
+ }
+ }
+
+ /*
+ * Invalidate the relcache so that the new partition is now included
+ * in rel's partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel,
+ classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool isnull,
+ new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ /* Update pg_class tuple */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(partRel)));
+ Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
+
+ (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
+ &isnull);
+ Assert(!isnull);
+
+ /* Clear relpartbound and reset relispartition */
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+ new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
+ new_null[Anum_pg_class_relpartbound - 1] = true;
+ new_repl[Anum_pg_class_relpartbound - 1] = true;
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
+ simple_heap_update(classRel, &newtuple->t_self, newtuple);
+ CatalogUpdateIndexes(classRel, newtuple);
+ heap_freetuple(newtuple);
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..5e3989a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
/*
* Finally create the relation. This also creates the type.
*/
- DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
+ DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
+ NULL);
return address;
}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 325a810..c6b0e4f 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
- address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
+ address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
+ NULL);
Assert(address.objectId != InvalidOid);
return address;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c978c0..28d0036 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3031,6 +3031,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4215,6 +4216,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionBoundSpec *
+_copyPartitionBoundSpec(const PartitionBoundSpec *from)
+{
+ PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(listdatums);
+ COPY_NODE_FIELD(lowerdatums);
+ COPY_NODE_FIELD(upperdatums);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeDatum *
+_copyPartitionRangeDatum(const PartitionRangeDatum *from)
+{
+ PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
+
+ COPY_SCALAR_FIELD(infinite);
+ COPY_NODE_FIELD(value);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5138,6 +5176,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionBoundSpec:
+ retval = _copyPartitionBoundSpec(from);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _copyPartitionRangeDatum(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 7d0391d..8fc32ca 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2668,6 +2669,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(listdatums);
+ COMPARE_NODE_FIELD(lowerdatums);
+ COMPARE_NODE_FIELD(upperdatums);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
+{
+ COMPARE_SCALAR_FIELD(infinite);
+ COMPARE_NODE_FIELD(value);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3430,6 +3462,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionBoundSpec:
+ retval = _equalPartitionBoundSpec(a, b);
+ break;
+ case T_PartitionRangeDatum:
+ retval = _equalPartitionRangeDatum(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3997441..973fb15 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
/* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break;
+ case T_PartitionBoundSpec:
+ loc = ((const PartitionBoundSpec *) expr)->location;
+ break;
+ case T_PartitionRangeDatum:
+ loc = ((const PartitionRangeDatum *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 323daf5..0d858f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3300,6 +3301,26 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUND");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(listdatums);
+ WRITE_NODE_FIELD(lowerdatums);
+ WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
+{
+ WRITE_NODE_TYPE("PARTRANGEDATUM");
+
+ WRITE_BOOL_FIELD(infinite);
+ WRITE_NODE_FIELD(value);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3893,6 +3914,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionBoundSpec:
+ _outPartitionBoundSpec(str, obj);
+ break;
+ case T_PartitionRangeDatum:
+ _outPartitionRangeDatum(str, obj);
+ break;
default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 917e6c8..c587d4e 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2266,6 +2266,36 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionBoundSpec
+ */
+static PartitionBoundSpec *
+_readPartitionBoundSpec(void)
+{
+ READ_LOCALS(PartitionBoundSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_NODE_FIELD(listdatums);
+ READ_NODE_FIELD(lowerdatums);
+ READ_NODE_FIELD(upperdatums);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeDatum
+ */
+static PartitionRangeDatum *
+_readPartitionRangeDatum(void)
+{
+ READ_LOCALS(PartitionRangeDatum);
+
+ READ_BOOL_FIELD(infinite);
+ READ_NODE_FIELD(value);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2497,6 +2527,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONBOUND", 14))
+ return_value = _readPartitionBoundSpec();
+ else if (MATCH("PARTRANGEDATUM", 14))
+ return_value = _readPartitionRangeDatum();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2387df9..b458e99 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionSpec *partspec;
+ PartitionRangeDatum *partrange_datum;
}
%type <node> stmt schema_stmt
@@ -551,6 +552,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partbound_datum
+%type <list> partbound_datum_list
+%type <partrange_datum> PartitionRangeDatum
+%type <list> range_datum_list
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -576,7 +584,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
@@ -592,7 +600,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
@@ -2378,6 +2387,31 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ 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;
+ }
;
alter_column_default:
@@ -2473,6 +2507,73 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partbound_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatums = $5;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ n->lowerdatums = $5;
+ n->upperdatums = $9;
+ n->location = @3;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+partbound_datum:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partbound_datum_list:
+ partbound_datum { $$ = list_make1($1); }
+ | partbound_datum_list ',' partbound_datum
+ { $$ = lappend($1, $3); }
+ ;
+
+range_datum_list:
+ PartitionRangeDatum { $$ = list_make1($1); }
+ | range_datum_list ',' PartitionRangeDatum
+ { $$ = lappend($1, $3); }
+ ;
+
+PartitionRangeDatum:
+ UNBOUNDED
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = true;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | partbound_datum
+ {
+ PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
+
+ n->infinite = false;
+ n->value = $1;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
/*****************************************************************************
*
@@ -2890,6 +2991,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionSpec OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partspec = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionSpec
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partspec = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2935,6 +3074,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2957,6 +3101,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2968,6 +3123,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -4555,6 +4715,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;
+ }
;
/*****************************************************************************
@@ -13804,6 +14006,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13850,6 +14053,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 666cc1f..4175ef5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -88,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -130,6 +134,7 @@ 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 transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
/*
@@ -253,7 +258,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
{
int partnatts = list_length(stmt->partspec->partParams);
- if (stmt->inhRelations)
+ if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
@@ -2580,6 +2585,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2662,6 +2668,19 @@ 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;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3026,3 +3045,242 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of rel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* tranform the values */
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+ cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+ *result_spec;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+
+ result_spec = copyObject(spec);
+ switch (strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ {
+ ListCell *cell;
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ result_spec->listdatums = NIL;
+ foreach(cell, spec->listdatums)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+ ListCell *cell2;
+ bool duplicate;
+
+ value = (Node *) make_const(pstate, &con->val, con->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(pstate,
+ exprLocation((Node *) con))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ /* Don't add to the result if the value is a duplicate */
+ duplicate = false;
+ foreach(cell2, result_spec->listdatums)
+ {
+ Const *value2 = (Const *) lfirst(cell2);
+
+ if (equal(value, value2))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+
+ result_spec->listdatums = lappend(result_spec->listdatums,
+ value);
+ }
+
+ break;
+ }
+
+ case PARTITION_STRATEGY_RANGE:
+ {
+ ListCell *cell1,
+ *cell2;
+ int i,
+ j;
+ char *colname;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+ if (list_length(spec->lowerdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("FROM must specify exactly one value per partitioning column")));
+ if (list_length(spec->upperdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("TO must specify exactly one value per partitioning column")));
+
+ i = j = 0;
+ result_spec->lowerdatums = result_spec->upperdatums = NIL;
+ forboth (cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum,
+ *rdatum;
+ Node *value;
+ A_Const *lcon = NULL,
+ *rcon = NULL;
+
+ ldatum = (PartitionRangeDatum *) lfirst(cell1);
+ rdatum = (PartitionRangeDatum *) lfirst(cell2);
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ if (!ldatum->infinite)
+ lcon = (A_Const *) ldatum->value;
+ if (!rdatum->infinite)
+ rcon = (A_Const *) rdatum->value;
+
+ if (lcon)
+ {
+ value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ ldatum->value = value;
+ }
+
+ if (rcon)
+ {
+ value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ rdatum->value = value;
+ }
+
+ result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+ copyObject(ldatum));
+ result_spec->upperdatums = lappend(result_spec->upperdatums,
+ copyObject(rdatum));
+
+ ++i;
+ }
+
+ break;
+ }
+ }
+
+ return (Node *) result_spec;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f50ce40..fd4eff4 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_RELATION,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_FOREIGN_TABLE,
- InvalidOid, NULL);
+ InvalidOid, NULL,
+ queryString);
CreateForeignTable((CreateForeignTableStmt *) stmt,
address.objectId);
EventTriggerCollectSimpleCommand(address,
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2d16ea..eb16f70 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -282,6 +283,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
/*
@@ -1161,6 +1164,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ /*
+ * Same oids? If the partitioning structure did not change, that is,
+ * no partitions were added or removed to the relation, the oids array
+ * should still match element-by-element.
+ */
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+ }
+
+ /*
+ * Now compare partition bound collections. The logic to iterate over
+ * the collections is local to partition.c.
+ */
+ if (pdesc1->boundinfo != NULL)
+ {
+ if (pdesc2->boundinfo == NULL)
+ return false;
+
+ if (!partition_bounds_equal(key,
+ pdesc1->boundinfo, pdesc2->boundinfo))
+ return false;
+ }
+ else if (pdesc2->boundinfo != NULL)
+ return false;
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1288,13 +1343,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
- /* if it's a partitioned table, initialize key info */
+ /* if a partitioned table, initialize key and partition descriptor info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
{
relation->rd_partkeycxt = NULL;
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
}
/*
@@ -2291,6 +2351,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2439,11 +2503,12 @@ RelationClearRelation(Relation relation, bool rebuild)
*
* When rebuilding an open relcache entry, we must preserve ref count,
* rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also
- * attempt to preserve the pg_class entry (rd_rel), tupledesc, and
- * rewrite-rule substructures in place, because various places assume
- * that these structures won't move while they are working with an
- * open relcache entry. (Note: the refcount mechanism for tupledescs
- * might someday allow us to remove this hack for the tupledesc.)
+ * attempt to preserve the pg_class entry (rd_rel), tupledesc,
+ * rewrite-rule, and partition descriptor substructures in place,
+ * because various places assume that these structures won't move while
+ * they are working with an open relcache entry. (Note: the refcount
+ * mechanism for tupledescs might someday allow us to remove this hack
+ * for the tupledesc.)
*
* Note that this process does not touch CurrentResourceOwner; which
* is good because whatever ref counts the entry may have do not
@@ -2454,6 +2519,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2484,6 +2550,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2539,6 +2608,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 */
@@ -3773,6 +3849,9 @@ RelationCacheInitializePhase3(void)
RelationBuildPartitionKey(relation);
Assert(relation->rd_partkey != NULL);
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
restart = true;
}
@@ -5301,6 +5380,8 @@ load_relcache_init_file(bool shared)
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 11b16a9..77dc198 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,5 +143,6 @@ extern void StorePartitionKey(Relation rel,
Oid *partopclass,
Oid *partcollation);
extern void RemovePartitionKeyByRelId(Oid relid);
+extern void StorePartitionBound(Relation rel, Node *bound);
#endif /* HEAP_H */
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..062de88
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "parser/parse_node.h"
+#include "utils/rel.h"
+
+/*
+ * BoundCollection encapsulates a set of partition bounds. It is usually
+ * associated with partitioned tables as part of its partition descriptor.
+ *
+ * The internal structure is opaque outside partition.c.
+ */
+typedef struct BoundCollectionData *BoundCollection;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* Number of partitions */
+ Oid *oids; /* OIDs of partitions */
+ BoundCollection boundinfo; /* collection of partition bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+extern void RelationBuildPartitionDesc(Relation relation);
+extern bool partition_bounds_equal(PartitionKey key,
+ BoundCollection p1, BoundCollection p2);
+
+extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
+extern Oid get_partition_parent(Oid relid);
+extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
+extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index ba0f745..62a9377 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
* not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
+ bool relispartition; /* is relation a partition? */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
@@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
aclitem relacl[1]; /* access permissions */
text reloptions[1]; /* access-method-specific options */
+ pg_node_tree relpartbound; /* partition bound node tree */
#endif
} FormData_pg_class;
@@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 31
+#define Natts_pg_class 33
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
-#define Anum_pg_class_relfrozenxid 28
-#define Anum_pg_class_relminmxid 29
-#define Anum_pg_class_relacl 30
-#define Anum_pg_class_reloptions 31
+#define Anum_pg_class_relispartition 28
+#define Anum_pg_class_relfrozenxid 29
+#define Anum_pg_class_relminmxid 30
+#define Anum_pg_class_relacl 31
+#define Anum_pg_class_reloptions 32
+#define Anum_pg_class_relpartbound 33
/* ----------------
* initial contents of pg_class
@@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 7a770f4..fa48f2e 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -23,7 +23,7 @@
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
- ObjectAddress *typaddress);
+ ObjectAddress *typaddress, const char *queryString);
extern void RemoveRelations(DropStmt *drop);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b27412c..c514d3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -456,6 +457,8 @@ typedef enum NodeTag
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
+ T_PartitionBoundSpec,
+ T_PartitionRangeDatum,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d30c82b..427eff2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -728,6 +728,51 @@ typedef struct PartitionSpec
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
+/*
+ * PartitionBoundSpec - a partition bound specification
+ */
+typedef struct PartitionBoundSpec
+{
+ NodeTag type;
+
+ char strategy;
+
+ /* List partition values */
+ List *listdatums;
+
+ /*
+ * Range partition lower and upper bounds; each member of the lists
+ * is a PartitionRangeDatum (see below).
+ */
+ List *lowerdatums;
+ List *upperdatums;
+
+ int location;
+} PartitionBoundSpec;
+
+/*
+ * PartitionRangeDatum
+ */
+typedef struct PartitionRangeDatum
+{
+ NodeTag type;
+
+ bool infinite;
+ Node *value;
+
+ int location;
+} PartitionRangeDatum;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1577,7 +1622,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
@@ -1803,7 +1850,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
- PartitionSpec *partspec; /* PARTITION BY clause */
+ Node *partbound; /* FOR VALUES clause */
+ PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 77d873b..581ff6e 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)
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index be3b6f7..783bb00 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
+ Node *bound);
#endif /* PARSE_UTILCMD_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 60d8de3..cd7ea1d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -125,6 +125,9 @@ typedef struct RelationData
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -602,6 +605,24 @@ get_partition_col_attnum(PartitionKey key, int col)
return key->partattrs[col];
}
+static inline Oid
+get_partition_col_typid(PartitionKey key, int col)
+{
+ return key->parttypid[col];
+}
+
+static inline int32
+get_partition_col_typmod(PartitionKey key, int col)
+{
+ return key->parttypmod[col];
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index fb492ad..0672018 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3017,3 +3017,301 @@ ERROR: cannot inherit from partitioned table "partitioned"
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, foo;
+--
+-- ATTACH PARTITION
+--
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 FROM (1) TO (10);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
+ ^
+DROP TABLE fail_part;
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+ERROR: relation "nonexistant" does not exist
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+ERROR: must be owner of relation not_owned_by_me
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ERROR: cannot attach inheritance child as partition
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+ERROR: cannot attach inheritance parent as partition
+DROP TABLE parent CASCADE;
+NOTICE: drop cascades to table child
+-- check that 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 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 that the table being attached has only columns present 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: New partition should contain only the columns present in parent.
+DROP TABLE fail_part;
+-- check that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing column "b"
+DROP TABLE fail_part;
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child 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: child table "fail_part" has different collation for column "b"
+DROP TABLE fail_part;
+-- check that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: child table is missing constraint "check_a"
+-- check that the constraint 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: child table "fail_part" has different definition for check constraint "check_a"
+DROP TABLE fail_part;
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+ attislocal | attinhcount
+------------+-------------
+ f | 1
+ f | 1
+(2 rows)
+
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+ERROR: partition "fail_part" would overlap partition "part_1"
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+ a int,
+ b char
+) PARTITION BY LIST (a);
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR: partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+ LIKE list_parted2,
+ CONSTRAINT check_a CHECK (a IN (3))
+);
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+NOTICE: skipping scan to validate partition constraint
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b);
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+ a int CHECK (a = 1),
+ b int CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+ERROR: partition constraint is violated by some row
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+ a int CHECK (a = 1),
+ b int CHECK (b >= 10 AND b < 18)
+);
+-- however, range partition key cannot contain NULLs, so there should be
+-- explicit NOT NULL constraints on the key columns for the validation scan
+-- to be skipped
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- set a and b to NOT NULL and the validation scan will be skipped
+ALTER TABLE range_parted DETACH PARTITION part2;
+ALTER TABLE part2 ALTER a SET NOT NULL, ALTER b SET NOT NULL;
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+NOTICE: skipping scan to validate partition constraint
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+ LIKE list_parted2
+) PARTITION BY LIST (b);
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+ERROR: partition constraint is violated by some row
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+NOTICE: skipping scan to validate partition constraint
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+ERROR: "part_2" is already a partition
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ERROR: circular inheritance not allowed
+DETAIL: "part_5" is already a child of "list_parted2".
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+ERROR: circular inheritance not allowed
+DETAIL: "list_parted2" is already a child of "list_parted2".
+--
+-- DETACH PARTITION
+--
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+ERROR: relation "part_4" does not exist
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ERROR: relation "not_a_part" is not a partition of relation "list_parted2"
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+ERROR: relation "part_1" is not a partition of relation "list_parted2"
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+ attinhcount | attislocal
+-------------+------------
+ 0 | t
+ 0 | t
+(2 rows)
+
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+ coninhcount | conislocal
+-------------+------------
+ 0 | t
+(1 row)
+
+DROP TABLE part_3_4;
+-- Check ALTER TABLE commands for partitioned tables and partitions
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ERROR: column must be added to child tables too
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+ERROR: column must be dropped from child tables too
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ERROR: cannot add column to a partition
+ALTER TABLE part_2 DROP COLUMN b;
+ERROR: cannot drop inherited column "b"
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ERROR: cannot rename inherited column "b"
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+ERROR: cannot alter inherited column "b"
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ERROR: constraint must be added to child tables too
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ERROR: constraint must be added to child tables too
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted2"
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ERROR: column "b" is marked NOT NULL in parent table
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+ERROR: cannot drop inherited constraint "check_a2" of relation "part_2"
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ERROR: constraint must be dropped from child tables too
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+ERROR: constraint must be dropped from child tables too
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+ERROR: cannot inherit from partition "part_2"
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ERROR: cannot inherit from a partition
+ALTER TABLE part_2 INHERIT inh_test;
+ERROR: cannot change inheritance of a partition
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ERROR: cannot drop column named in partition key
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+ERROR: cannot alter type of column named in partition key
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to table part1
+drop cascades to table part2
+drop cascades to table part_2
+drop cascades to table part_5
+drop cascades to table part_5_a
+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 0f15c98..01124e1 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -429,3 +429,190 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
Partition key: LIST ((a + 1))
DROP TABLE partitioned, partitioned2;
+--
+-- Partitions
+--
+-- check partition bound syntax
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ERROR: syntax error at or near "int"
+LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+ ^
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ERROR: syntax error at or near "::"
+LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ERROR: syntax error at or near ")"
+LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+ERROR: invalid bound specification for a list partition
+LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ERROR: specified value cannot be cast to type "boolean" of column "a"
+LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+ ^
+DROP TABLE bools;
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ERROR: invalid bound specification for a range partition
+LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+ ^
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: FROM must specify exactly one value per partitioning column
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+ERROR: TO must specify exactly one value per partitioning column
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+ERROR: cannot specify NULL in range bound
+-- check if compatible with the specified parent
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+ERROR: cannot create table with OIDs as partition of table without OIDs
+DROP TABLE no_oids_parted;
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+ERROR: cannot create table without OIDs as partition of table with OIDs
+DROP TABLE oids_parted;
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+ERROR: partition "fail_part" would overlap partition "part_null_z"
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+ERROR: partition "fail_part" would overlap partition "part_ab"
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+ERROR: cannot create range partition with empty range
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+ERROR: cannot create range partition with empty range
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+ERROR: partition "fail_part" would overlap partition "part0"
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+ERROR: partition "fail_part" would overlap partition "part1"
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+ERROR: partition "fail_part" would overlap partition "part00"
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+ERROR: partition "fail_part" would overlap partition "part12"
+-- check schema propagation from parent
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+ conislocal | coninhcount
+------------+-------------
+ f | 1
+(1 row)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+ERROR: column "c" named in partition key does not exist
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- partitions cannot be dropped directly
+DROP TABLE part_a;
+-- need to specify CASCADE to drop partitions along with the parent
+DROP TABLE parted;
+ERROR: cannot drop table parted because other objects depend on it
+DETAIL: table part_b depends on table parted
+table part_c depends on table parted
+table part_c_1_10 depends on table part_c
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
+NOTICE: drop cascades to 14 other objects
+DETAIL: drop cascades to table part00
+drop cascades to table part10
+drop cascades to table part11
+drop cascades to table part12
+drop cascades to table part0
+drop cascades to table part1
+drop cascades to table part_null_z
+drop cascades to table part_ab
+drop cascades to table part_1
+drop cascades to table part_2
+drop cascades to table part_null
+drop cascades to table part_b
+drop cascades to table part_c
+drop cascades to table part_c_1_10
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index d929b4d..21f319a 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1904,3 +1904,269 @@ ALTER TABLE foo INHERIT partitioned;
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, foo;
+
+--
+-- ATTACH PARTITION
+--
+
+-- check that target table is 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 that partition bound is compatible
+CREATE TABLE list_parted (
+ a int NOT NULL,
+ b char(2) 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 FROM (1) TO (10);
+DROP TABLE fail_part;
+
+-- check that the table being attached exists
+ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
+
+-- check ownership of the source table
+CREATE ROLE regress_test_me;
+CREATE ROLE regress_test_not_me;
+CREATE TABLE not_owned_by_me (LIKE list_parted);
+ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
+SET SESSION AUTHORIZATION regress_test_me;
+CREATE TABLE owned_by_me (
+ a int
+) PARTITION BY LIST (a);
+ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
+RESET SESSION AUTHORIZATION;
+DROP TABLE owned_by_me, not_owned_by_me;
+DROP ROLE regress_test_not_me;
+DROP ROLE regress_test_me;
+
+-- check that the table being attached is not part of regular inheritance
+CREATE TABLE parent (LIKE list_parted);
+CREATE TABLE child () INHERITS (parent);
+ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
+ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
+DROP TABLE parent CASCADE;
+
+-- check that 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 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 that the table being attached has only columns present 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 that the table being attached has every column of the parent
+CREATE TABLE fail_part (a int NOT NULL);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check that columns match in type, collation and NOT NULL status
+CREATE TABLE fail_part (
+ b char(3),
+ a int NOT NULL
+);
+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 that the table being attached has all constraints of the parent
+CREATE TABLE fail_part (
+ b char(2) COLLATE "en_US",
+ a int NOT NULL
+);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check that the constraint matches in definition with parent's constraint
+ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+DROP TABLE fail_part;
+
+-- check the attributes and constraints after partition is attached
+CREATE TABLE part_1 (
+ a int NOT NULL,
+ b char(2) COLLATE "en_US",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
+-- attislocal and conislocal are always false for merged attributes and constraints respectively.
+SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+
+-- check that the new partition won't overlap with an existing partition
+CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
+
+-- check validation when attaching list partitions
+CREATE TABLE list_parted2 (
+ a int,
+ b char
+) PARTITION BY LIST (a);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_2 (LIKE list_parted2);
+INSERT INTO part_2 VALUES (3, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- should be ok after deleting the bad row
+DELETE FROM part_2;
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part_3_4 (
+ LIKE list_parted2,
+ CONSTRAINT check_a CHECK (a IN (3))
+);
+
+-- however, if a list partition does not accept nulls, there should be
+-- an explicit NOT NULL constraint on the partition key column for the
+-- validation scan to be skipped;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+-- adding a NOT NULL constraint will cause the scan to be skipped
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+ALTER TABLE part_3_4 ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+
+
+-- check validation when attaching range partitions
+CREATE TABLE range_parted (
+ a int,
+ b int
+) PARTITION BY RANGE (a, b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part1 (
+ a int CHECK (a = 1),
+ b int CHECK (b >= 1 AND b <= 10)
+);
+INSERT INTO part1 VALUES (1, 10);
+-- Remember the TO bound is exclusive
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- should be ok after deleting the bad row
+DELETE FROM part1;
+ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
+
+-- adding constraints that describe the desired partition constraint
+-- (or more restrictive) will help skip the validation scan
+CREATE TABLE part2 (
+ a int CHECK (a = 1),
+ b int CHECK (b >= 10 AND b < 18)
+);
+
+-- however, range partition key cannot contain NULLs, so there should be
+-- explicit NOT NULL constraints on the key columns for the validation scan
+-- to be skipped
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- set a and b to NOT NULL and the validation scan will be skipped
+ALTER TABLE range_parted DETACH PARTITION part2;
+ALTER TABLE part2 ALTER a SET NOT NULL, ALTER b SET NOT NULL;
+ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+
+
+-- check that leaf partitions are scanned when attaching a partitioned
+-- table
+CREATE TABLE part_5 (
+ LIKE list_parted2
+) PARTITION BY LIST (b);
+
+-- check that violating rows are correctly reported
+CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
+INSERT INTO part_5_a (a, b) VALUES (6, 'a');
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+-- delete the faulting row and also add a constraint to skip the scan
+DELETE FROM part_5_a WHERE a NOT IN (3);
+ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
+ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
+
+
+-- check that the table being attached is not already a partition
+ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
+
+-- check that circular inheritance is not allowed
+ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
+ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
+
+--
+-- DETACH PARTITION
+--
+
+-- check that the partition being detached exists at all
+ALTER TABLE list_parted2 DETACH PARTITION part_4;
+
+-- check that the partition being detached is actually a partition of the parent
+CREATE TABLE not_a_part (a int);
+ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
+ALTER TABLE list_parted2 DETACH PARTITION part_1;
+
+-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
+-- attislocal/conislocal is set to true
+ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
+SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
+SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
+DROP TABLE part_3_4;
+
+-- Check ALTER TABLE commands for partitioned tables and partitions
+
+-- cannot add/drop column to/from *only* the parent
+ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
+ALTER TABLE ONLY list_parted2 DROP COLUMN b;
+
+-- cannot add a column to partition or drop an inherited one
+ALTER TABLE part_2 ADD COLUMN c text;
+ALTER TABLE part_2 DROP COLUMN b;
+
+-- Nor rename, alter type
+ALTER TABLE part_2 RENAME COLUMN b to c;
+ALTER TABLE part_2 ALTER COLUMN b TYPE text;
+
+-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
+ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
+ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
+ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
+
+-- cannot drop inherited NOT NULL or check constraints from partition
+ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
+ALTER TABLE part_2 ALTER b DROP NOT NULL;
+ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+
+-- cannot drop NOT NULL or check constraints from *only* the parent
+ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
+ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
+
+-- check that a partition cannot participate in regular inheritance
+CREATE TABLE inh_test () INHERITS (part_2);
+CREATE TABLE inh_test (LIKE part_2);
+ALTER TABLE inh_test INHERIT part_2;
+ALTER TABLE part_2 INHERIT inh_test;
+
+-- cannot drop or alter type of partition key columns of lower level
+-- partitioned tables; for example, part_5, which is list_parted2's
+-- partition, is partitioned on b;
+ALTER TABLE list_parted2 DROP COLUMN b;
+ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
+
+-- cleanup
+DROP TABLE list_parted, list_parted2, range_parted CASCADE;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index f100498..683b852 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -416,3 +416,156 @@ CREATE TABLE fail () INHERITS (partitioned2);
\d partitioned2
DROP TABLE partitioned, partitioned2;
+
+--
+-- Partitions
+--
+
+-- check partition bound syntax
+
+CREATE TABLE list_parted (
+ a int
+) PARTITION BY LIST (a);
+-- syntax allows only string literal, numeric literal and null to be
+-- specified for a partition bound value
+CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
+CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
+CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE bools (
+ a bool
+) PARTITION BY LIST (a);
+CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
+DROP TABLE bools;
+
+CREATE TABLE range_parted (
+ a date
+) PARTITION BY RANGE (a);
+
+-- trying to specify list for range partitioned table
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
+-- each of start and end bounds must have same number of values as the
+-- length of the partition key
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
+
+-- cannot specify null values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+
+-- check if compatible with the specified parent
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE unparted (
+ a int
+);
+CREATE TABLE fail_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 fail_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
+) PARTITION BY RANGE (a) WITHOUT OIDS;
+CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
+DROP TABLE no_oids_parted;
+
+-- likewise, the reverse if also true
+CREATE TABLE oids_parted (
+ a int
+) PARTITION BY RANGE (a) WITH OIDS;
+CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
+DROP TABLE oids_parted;
+
+-- check for partition bound overlap and other invalid specifications
+
+CREATE TABLE list_parted2 (
+ a varchar
+) PARTITION BY LIST (a);
+CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
+CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
+
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
+
+CREATE TABLE range_parted2 (
+ a int
+) PARTITION BY RANGE (a);
+
+-- trying to create range partition with empty range
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
+-- note that the range '[1, 1)' has no elements
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
+
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+
+-- now check for multi-column range partition key
+CREATE TABLE range_parted3 (
+ a int,
+ b int
+) PARTITION BY RANGE (a, (b+1));
+
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+
+-- cannot create a partition that says column b is allowed to range
+-- from -infinity to +infinity, while there exist partitions that have
+-- more specific ranges
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (
+ a text,
+ b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) PARTITION BY LIST (a);
+
+CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
+
+-- able to specify column default, column constraint, and table constraint
+CREATE TABLE part_b PARTITION OF parted (
+ b WITH OPTIONS NOT NULL DEFAULT 1 CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('b');
+-- conislocal should be false for any merged constraints
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
+CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- partitions 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
[text/x-diff] 0004-psql-and-pg_dump-support-for-partitions-14.patch (22.1K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/5-0004-psql-and-pg_dump-support-for-partitions-14.patch)
download | inline diff:
From c1c61624561a7ec97d0ef378f4f66ba4ec5fe032 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 | 82 +++++++++++++++++++
src/bin/pg_dump/common.c | 86 ++++++++++++++++++++
src/bin/pg_dump/pg_dump.c | 118 ++++++++++++++++++++++++++--
src/bin/pg_dump/pg_dump.h | 12 +++
src/bin/psql/describe.c | 85 +++++++++++++++++---
src/test/regress/expected/create_table.out | 40 ++++++++++
src/test/regress/sql/create_table.sql | 12 +++
7 files changed, 415 insertions(+), 20 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9004878..99add8e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8447,6 +8447,88 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionBoundSpec:
+ {
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ switch (spec->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ Assert(spec->listdatums != NIL);
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, spec->listdatums)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ break;
+
+ case PARTITION_STRATEGY_RANGE:
+ Assert(spec->lowerdatums != NIL &&
+ spec->upperdatums != NIL &&
+ list_length(spec->lowerdatums) ==
+ list_length(spec->upperdatums));
+
+ appendStringInfoString(buf, "FOR VALUES");
+ appendStringInfoString(buf, " FROM");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->lowerdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ appendStringInfoString(buf, " TO");
+ appendStringInfoString(buf, " (");
+ sep = "";
+ foreach (cell, spec->upperdatums)
+ {
+ PartitionRangeDatum *datum = lfirst(cell);
+ Const *val;
+
+ appendStringInfoString(buf, sep);
+ if (datum->infinite)
+ appendStringInfoString(buf, "UNBOUNDED");
+ else
+ {
+ val = (Const *) datum->value;
+ get_const_expr(val, context, -1);
+ }
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+ break;
+
+ default:
+ elog(ERROR, "unrecognized partition strategy: %d",
+ (int) spec->strategy);
+ break;
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 3e20f02..22f1806 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);
@@ -323,6 +338,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
*
@@ -924,6 +976,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 fb92e7f..57e626c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5608,9 +5608,16 @@ getInherits(Archive *fout, int *numInherits)
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
- /* find all the inheritance information */
-
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ /*
+ * Find all the inheritance information, excluding implicit inheritance
+ * via partitioning. We handle that case using getPartitions(), because
+ * we want more information about partitions than just the parent-child
+ * relationship.
+ */
+ 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);
@@ -5637,6 +5644,70 @@ 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;
+
+ /* Before version 10, there are no partitions */
+ if (fout->remoteVersion < 100000)
+ {
+ *numPartitions = 0;
+ return NULL;
+ }
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find the inheritance and boundary information about partitions */
+
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid as partrelid, inhparent AS partparent,"
+ " pg_get_expr(relpartbound, inhrelid) AS partbound"
+ " FROM pg_class c, pg_inherits"
+ " WHERE c.oid = inhrelid AND c.relispartition");
+
+ 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
*
@@ -14168,6 +14239,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 */
@@ -14196,8 +14278,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;
@@ -14226,7 +14311,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");
}
@@ -14284,15 +14370,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 (");
@@ -14462,6 +14555,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 f33f86d..dd5ad8f 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -321,6 +321,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
@@ -461,6 +463,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 9b08bae..0d34927 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1774,6 +1774,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(c.relpartbound, inhrelid)"
+ " FROM pg_catalog.pg_class c"
+ " JOIN pg_catalog.pg_inherits"
+ " ON c.oid = inhrelid"
+ " WHERE c.oid = '%s' AND c.relispartition;", 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 (tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2535,8 +2563,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)
@@ -2565,9 +2597,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
- 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);
+ /* print child tables (with additional info if partitions) */
+ if (pset.sversion >= 100000)
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
+ " 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')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
+ else 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' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
+ " 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);
@@ -2582,24 +2628,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
- printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ if (tableinfo.relkind != 'P')
+ printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
+ else
+ printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
- const char *ct = _("Child tables");
+ const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
- if (i == 0)
- printfPQExpBuffer(&buf, "%s: %s",
- ct, PQgetvalue(result, i, 0));
+ if (tableinfo.relkind != 'P')
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s",
+ ct, PQgetvalue(result, i, 0));
+ else
+ printfPQExpBuffer(&buf, "%*s %s",
+ ctw, "", PQgetvalue(result, i, 0));
+ }
else
- printfPQExpBuffer(&buf, "%*s %s",
- ctw, "", PQgetvalue(result, i, 0));
+ {
+ if (i == 0)
+ printfPQExpBuffer(&buf, "%s: %s %s",
+ ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ else
+ printfPQExpBuffer(&buf, "%*s %s %s",
+ ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
+ }
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 01124e1..1f56bcb 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -591,6 +591,46 @@ 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 level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+ Table "public.part_b"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 1
+Partition of: parted FOR VALUES IN ('b')
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+ "part_b_b_check" CHECK (b >= 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+ Table "public.part_c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition of: parted FOR VALUES IN ('c')
+Partition key: RANGE (b)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 1 (Use \d+ to list them.)
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Check constraints:
+ "check_a" CHECK (length(a) > 0)
+Number of partitions: 3 (Use \d+ to list them.)
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
-- need to specify CASCADE to drop partitions along with the parent
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 683b852..c28b7b3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -562,6 +562,18 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
-- create a level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+
-- partitions cannot be dropped directly
DROP TABLE part_a;
--
1.7.1
[text/x-diff] 0005-Teach-a-few-places-to-use-partition-check-quals-14.patch (30.9K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/6-0005-Teach-a-few-places-to-use-partition-check-quals-14.patch)
download | inline diff:
From 846fad4219386ebe6078353ded425ccfcf8fd528 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 quals.
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 | 3 +-
src/backend/executor/execMain.c | 75 +++++++++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/optimizer/util/plancat.c | 20 +++
src/include/nodes/execnodes.h | 4 +
src/test/regress/expected/inherit.out | 271 ++++++++++++++++++++++++++++++++
src/test/regress/expected/insert.out | 82 ++++++++++
src/test/regress/expected/update.out | 27 +++
src/test/regress/sql/inherit.sql | 48 ++++++
src/test/regress/sql/insert.sql | 59 +++++++
src/test/regress/sql/update.sql | 21 +++
11 files changed, 608 insertions(+), 6 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28b6f63..7a2bf94 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2519,7 +2519,8 @@ CopyFrom(CopyState cstate)
else
{
/* 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 9773272..ea3f59a 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 =
+ RelationGetPartitionQual(resultRelationDesc, true);
}
/*
@@ -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,28 @@ 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 for relation \"%s\" violates partition constraint",
+ 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 0668462..a612b08 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -369,7 +369,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)
@@ -922,7 +922,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 ad07baa..a2cbf14 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"
@@ -1139,6 +1140,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.
@@ -1224,6 +1226,24 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition predicates, if any */
+ pcqual = RelationGetPartitionQual(relation, true);
+ 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 f6f73f3..ff8b66b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -320,6 +320,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
@@ -344,6 +346,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/inherit.out b/src/test/regress/expected/inherit.out
index b331828..28ecc2c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1542,3 +1542,274 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+explain (costs off) select * from list_parted;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ -> Seq Scan on part_ab_cd
+ -> Seq Scan on part_ef_gh
+ -> Seq Scan on part_null_xy
+(5 rows)
+
+explain (costs off) select * from list_parted where a is null;
+ QUERY PLAN
+--------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NULL)
+(5 rows)
+
+explain (costs off) select * from list_parted where a is not null;
+ QUERY PLAN
+---------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ab_cd
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_ef_gh
+ Filter: (a IS NOT NULL)
+ -> Seq Scan on part_null_xy
+ Filter: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+ QUERY PLAN
+----------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+ -> Seq Scan on part_ef_gh
+ Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
+(7 rows)
+
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ab_cd
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_ef_gh
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ -> Seq Scan on part_null_xy
+ Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+(9 rows)
+
+explain (costs off) select * from list_parted where a = 'ab';
+ QUERY PLAN
+------------------------------------------
+ Append
+ -> Seq Scan on list_parted
+ Filter: ((a)::text = 'ab'::text)
+ -> Seq Scan on part_ab_cd
+ Filter: ((a)::text = 'ab'::text)
+(5 rows)
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+explain (costs off) select * from range_list_parted;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ -> Seq Scan on part_1_10
+ -> Seq Scan on part_10_20
+ -> Seq Scan on part_21_30_inc
+ -> Seq Scan on part_40_inf
+ -> Seq Scan on part_1_10_ab
+ -> Seq Scan on part_1_10_cd
+ -> Seq Scan on part_10_20_ab
+ -> Seq Scan on part_10_20_cd
+ -> Seq Scan on part_21_30_inc_ab
+ -> Seq Scan on part_21_30_inc_cd
+ -> Seq Scan on part_40_inf_ab
+ -> Seq Scan on part_40_inf_cd
+ -> Seq Scan on part_40_inf_null
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a = 5;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_ab
+ Filter: (a = 5)
+ -> Seq Scan on part_1_10_cd
+ Filter: (a = 5)
+(9 rows)
+
+explain (costs off) select * from range_list_parted where b = 'ab';
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_1_10_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_10_20_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: (b = 'ab'::bpchar)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (b = 'ab'::bpchar)
+(19 rows)
+
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+ QUERY PLAN
+-----------------------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
+(15 rows)
+
+explain (costs off) select * from range_list_parted where a is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a IS NULL)
+(3 rows)
+
+explain (costs off) select * from range_list_parted where b is null;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (b IS NULL)
+ -> Seq Scan on part_1_10
+ Filter: (b IS NULL)
+ -> Seq Scan on part_10_20
+ Filter: (b IS NULL)
+ -> Seq Scan on part_21_30_inc
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf
+ Filter: (b IS NULL)
+ -> Seq Scan on part_40_inf_null
+ Filter: (b IS NULL)
+(13 rows)
+
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+ QUERY PLAN
+------------------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_1_10_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_10_20_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_21_30_inc_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_ab
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_cd
+ Filter: ((a IS NOT NULL) AND (a < 67))
+ -> Seq Scan on part_40_inf_null
+ Filter: ((a IS NOT NULL) AND (a < 67))
+(29 rows)
+
+explain (costs off) select * from range_list_parted where a >= 30;
+ QUERY PLAN
+-------------------------------------
+ Append
+ -> Seq Scan on range_list_parted
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_ab
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_cd
+ Filter: (a >= 30)
+ -> Seq Scan on part_40_inf_null
+ Filter: (a >= 30)
+(11 rows)
+
+drop table list_parted cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table part_ab_cd
+drop cascades to table part_ef_gh
+drop cascades to table part_null_xy
+drop table range_list_parted cascade;
+NOTICE: drop cascades to 13 other objects
+DETAIL: drop cascades to table part_1_10
+drop cascades to table part_1_10_ab
+drop cascades to table part_1_10_cd
+drop cascades to table part_10_20
+drop cascades to table part_10_20_ab
+drop cascades to table part_10_20_cd
+drop cascades to table part_21_30_inc
+drop cascades to table part_21_30_inc_ab
+drop cascades to table part_21_30_inc_cd
+drop cascades to table part_40_inf
+drop cascades to table part_40_inf_ab
+drop cascades to table part_40_inf_cd
+drop cascades to table part_40_inf_null
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 03619d7..9ae6b09 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -160,3 +160,85 @@ 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+-- fail
+insert into part_a_1_a_10 values ('a', 11);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (a, 11).
+insert into part_a_1_a_10 values ('b', 1);
+ERROR: new row for relation "part_a_1_a_10" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+DETAIL: Failing row contains (b, 21).
+insert into part_b_10_b_20 values ('a', 10);
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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 for relation "part_b_10_b_20" violates partition constraint
+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');
+create table part_null partition of list_parted FOR VALUES IN (null);
+-- fail
+insert into part_AA_BB values ('cc', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (cc, 1).
+insert into part_AA_BB values ('AAa', 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (AAa, 1).
+-- ok
+insert into part_CC_DD values ('cC', 1);
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+ERROR: new row for relation "part_aa_bb" violates partition constraint
+DETAIL: Failing row contains (null, 1).
+-- ok
+insert into part_null values (null, 0);
+-- 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 from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (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 for relation "part_ee_ff_1_10" violates partition constraint
+DETAIL: Failing row contains (EE, 11).
+insert into part_EE_FF_1_10 values ('cc', 1);
+ERROR: new row for relation "part_ee_ff_1_10" violates partition constraint
+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 6 other objects
+DETAIL: drop cascades to table part_aa_bb
+drop cascades to table part_cc_dd
+drop cascades to table part_null
+drop cascades to 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..bdb4e2c 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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 for relation "part_a_1_a_10" violates partition constraint
+DETAIL: Failing row contains (b, 1).
+update range_parted set b = b - 1 where b = 10;
+ERROR: new row for relation "part_b_10_b_20" violates partition constraint
+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/inherit.sql b/src/test/regress/sql/inherit.sql
index f45aab1..aad62af 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -536,3 +536,51 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
+
+--
+-- Check that constraint exclusion works correctly with partitions using
+-- implicit constraints generated from the partition bound information.
+--
+create table list_parted (
+ a varchar
+) partition by list (a);
+create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
+create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
+create table part_null_xy partition of list_parted for values in (null, 'xy');
+
+explain (costs off) select * from list_parted;
+explain (costs off) select * from list_parted where a is null;
+explain (costs off) select * from list_parted where a is not null;
+explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
+explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
+explain (costs off) select * from list_parted where a = 'ab';
+
+create table range_list_parted (
+ a int,
+ b char(2)
+) partition by range (a);
+create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
+create table part_1_10_ab partition of part_1_10 for values in ('ab');
+create table part_1_10_cd partition of part_1_10 for values in ('cd');
+create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
+create table part_10_20_ab partition of part_10_20 for values in ('ab');
+create table part_10_20_cd partition of part_10_20 for values in ('cd');
+create table part_21_30_inc partition of range_list_parted for values from (21) to (30) partition by list (b);
+create table part_21_30_inc_ab partition of part_21_30_inc for values in ('ab');
+create table part_21_30_inc_cd partition of part_21_30_inc for values in ('cd');
+create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf_ab partition of part_40_inf for values in ('ab');
+create table part_40_inf_cd partition of part_40_inf for values in ('cd');
+create table part_40_inf_null partition of part_40_inf for values in (null);
+
+explain (costs off) select * from range_list_parted;
+explain (costs off) select * from range_list_parted where a = 5;
+explain (costs off) select * from range_list_parted where b = 'ab';
+explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
+explain (costs off) select * from range_list_parted where a is null;
+explain (costs off) select * from range_list_parted where b is null;
+explain (costs off) select * from range_list_parted where a is not null and a < 67;
+explain (costs off) select * from range_list_parted where a >= 30;
+
+drop table list_parted cascade;
+drop table range_list_parted cascade;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7924d5d..b6e821e 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -84,3 +84,62 @@ 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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');
+create table part_null partition of list_parted FOR VALUES IN (null);
+
+-- 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);
+
+-- fail (part_AA_BB does not allow nulls in its list of values)
+insert into part_AA_BB values (null, 1);
+-- ok
+insert into part_null values (null, 0);
+
+-- 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 from (1) to (10);
+create table part_EE_FF_10_20 partition of part_EE_FF for values from (10) to (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..5392fa5 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 from ('a', 1) to ('a', 10);
+create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
+create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('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
[text/x-diff] 0006-Introduce-a-PartitionTreeNode-data-structure-14.patch (8.0K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/7-0006-Introduce-a-PartitionTreeNode-data-structure-14.patch)
download | inline diff:
From f3c16c07eebe39710fdf721098d442f1ecd1397c 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 | 208 +++++++++++++++++++++++++++++++++++++++
src/include/catalog/partition.h | 5 +
2 files changed, 213 insertions(+), 0 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e21e7ad..a5800fb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -114,6 +114,61 @@ typedef struct PartitionListValue
int index;
} PartitionListValue;
+/*
+ * 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_parts 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;
+
static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
@@ -124,6 +179,9 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra
static List *generate_partition_qual(Relation rel, bool recurse);
+static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionTreeNode ptnode);
+
/* List partition related support functions */
static bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -874,6 +932,53 @@ RelationGetPartitionQual(Relation rel, bool recurse)
return generate_partition_qual(rel, recurse);
}
+/*
+ * 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 tree that are themselves partitioned tables.
+ */
+ root = GetPartitionTreeNodeRecurse(rel, 0);
+ root->index = 0; /* Root table has no parent */
+ root->num_leaf_parts = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_oids
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at ptnode
+ */
+List *
+get_leaf_partition_oids(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(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, ptnode->pdesc->oids[i]);
+ }
+
+ return result;
+}
+
/* Module-local functions */
/*
@@ -1314,6 +1419,109 @@ generate_partition_qual(Relation rel, bool recurse)
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;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ /* 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->oids[i];
+ int offset;
+ Relation rel;
+ PartitionTreeNode child;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Skip if a leaf partition */
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ heap_close(rel, AccessShareLock);
+ continue;
+ }
+
+ 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 062de88..057b1e3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -36,6 +36,7 @@ typedef struct PartitionDescData
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
+typedef struct PartitionTreeNodeData *PartitionTreeNode;
extern void RelationBuildPartitionDesc(Relation relation);
extern bool partition_bounds_equal(PartitionKey key,
@@ -45,4 +46,8 @@ extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound);
extern Oid get_partition_parent(Oid relid);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
+
+/* For tuple routing */
+extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
+extern List *get_leaf_partition_oids(PartitionTreeNode ptnode);
#endif /* PARTITION_H */
--
1.7.1
[text/x-diff] 0007-Tuple-routing-for-partitioned-tables-14.patch (43.2K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/8-0007-Tuple-routing-for-partitioned-tables-14.patch)
download | inline diff:
From 4cd487103538ac8cab74aa8aac75ffec4f42f02c 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 | 327 ++++++++++++++++++++++++++++++-
src/backend/commands/copy.c | 203 +++++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 49 +++++-
src/backend/executor/nodeModifyTable.c | 156 +++++++++++++++
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/plan/createplan.c | 77 +++++++
src/backend/optimizer/util/plancat.c | 13 ++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 7 +
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/nodes/plannodes.h | 1 +
src/include/optimizer/plancat.h | 1 +
src/test/regress/expected/insert.out | 52 +++++
src/test/regress/sql/insert.sql | 25 +++
18 files changed, 932 insertions(+), 7 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index a5800fb..e7e0c7c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -182,6 +182,18 @@ static List *generate_partition_qual(Relation rel, bool recurse);
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 bool equal_list_info(PartitionKey key,
PartitionListInfo *l1, PartitionListInfo *l2);
@@ -195,6 +207,8 @@ static PartitionRangeBound *copy_range_bound(PartitionKey key, PartitionRangeBou
static bool equal_range_info(PartitionKey key,
PartitionRangeInfo *r1, PartitionRangeInfo *r2);
static int32 partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg);
+static int32 partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg);
static bool partition_rbound_eq(PartitionKey key,
PartitionRangeBound *b1, PartitionRangeBound *b2);
typedef int32 (*partition_rbound_bsearch_cmp_fn) (PartitionKey,
@@ -1438,7 +1452,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;
@@ -1522,6 +1536,283 @@ 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 = RelationGetPartitionKey(rel);
+ 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)
+{
+ Relation partRel;
+ 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_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRATEGY_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRATEGY_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;
+ }
+
+ partRel = heap_open(ptnode->pdesc->oids[index], NoLock);
+
+ /* Don't recurse if the index'th partition is a leaf partition. */
+ if (partRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ {
+ PartitionTreeNode prev;
+
+ /*
+ * Index returned above considers only this parent (of which partRel
+ * is a partition). We however want to return the index across the
+ * the whole partition tree. If partRel is the leftmost partition
+ * of parent (index = 0) or if none of the left siblings are
+ * partitioned tables, we can simply return parent's offset plus
+ * partRel's index as the result. Otherwise, we find the node
+ * corresponding to the rightmost partitioned table among partRel's
+ * left siblings and use the information therein.
+ */
+ 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);
+
+ index = node->offset + node->num_leaf_parts +
+ (index - node->index - 1);
+ }
+ else
+ /*
+ * The easy case where all of partRel's left siblings (if any) are
+ * leaf partitions.
+ */
+ index = ptnode->offset + index;
+
+ heap_close(partRel, NoLock);
+ return index;
+ }
+
+ heap_close(partRel, NoLock);
+
+ /*
+ * Need to recurse as the selected partition is a partitioned table
+ * itself. Locate the corresponding PartitionTreeNode to pass 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 (arg 'value' contains the
+ * list partition key of the original tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ PartitionListInfo listinfo;
+ int found;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_LIST);
+ listinfo = pdesc->boundinfo->bounds.lists;
+
+ if (isnull && listinfo.has_null)
+ return listinfo.null_index;
+ else if (!isnull)
+ {
+ found = partition_list_values_bsearch(key,
+ listinfo.values,
+ listinfo.nvalues,
+ value);
+ if (found >= 0)
+ return listinfo.indexes[found];
+ }
+
+ /* Control reaches here if isnull and !listinfo->has_null */
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Get the index of the range partition for a tuple (arg 'tuple'
+ * actually contains the range partition key of the original
+ * tuple)
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple)
+{
+ int offset;
+ PartitionRangeInfo rangeinfo;
+
+ Assert(pdesc->nparts > 0);
+ Assert(pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ rangeinfo = pdesc->boundinfo->bounds.ranges;
+
+ offset = partition_rbound_bsearch(key,
+ rangeinfo.bounds, rangeinfo.nbounds,
+ tuple, partition_rbound_datum_cmp,
+ true, NULL);
+
+ /*
+ * Offset returned is such that the bound at offset is found to be less
+ * or equal with the tuple. That is, the tuple belongs to the partition
+ * with the rangeinfo.bounds[offset] as the lower bound and
+ * rangeinfo.bounds[offset+1] as the upper bound, provided the latter is
+ * indeed an upper (!lower) bound. If it turns out otherwise, the
+ * corresponding index will be -1, which means no valid partition exists.
+ */
+ return rangeinfo.indexes[offset+1];
+}
+
/* List partition related support functions */
/*
@@ -1767,6 +2058,40 @@ partition_rbound_cmp(PartitionKey key, PartitionRangeBound *b1, void *arg)
}
/*
+ * Return whether bound <=, =, >= partition key of tuple
+ *
+ * The 3rd argument is void * so that it can be used with
+ * partition_rbound_bsearch()
+ */
+static int32
+partition_rbound_datum_cmp(PartitionKey key, PartitionRangeBound *bound,
+ void *arg)
+{
+ Datum *datums1 = bound->datums,
+ *datums2 = (Datum *) arg;
+ int i;
+ int32 cmpval;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ if (bound->infinite[i])
+ return bound->lower ? -1 : 1;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->partcollation[i],
+ datums1[i], datums2[i]));
+ if (cmpval != 0)
+ break;
+ }
+
+ /* If datums are equal and this is an upper bound, tuple > bound */
+ if (cmpval == 0 && !bound->lower)
+ return -1;
+
+ return cmpval;
+}
+
+/*
* Return whether two range bounds are equal simply by comparing datums
*/
static bool
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7a2bf94..6fb376f 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.
@@ -1397,6 +1403,94 @@ BeginCopy(ParseState *pstate,
(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_TABLE)
+ {
+ List *leaf_parts;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_part_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_parts = get_leaf_partition_oids(cstate->ptnode);
+ num_leaf_parts = list_length(leaf_parts);
+
+ 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_part_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_parts)
+ {
+ Relation part_rel;
+
+ part_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation.
+ */
+ CheckValidResultRel(part_rel, CMD_INSERT);
+
+ InitResultRelInfo(leaf_part_rri,
+ part_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_part_rri, false);
+
+ /* Special dance for foreign tables */
+ if (leaf_part_rri->ri_FdwRoutine)
+ {
+ List *fdw_private;
+
+ fdw_rte->relid = RelationGetRelid(part_rel);
+ fdw_private = leaf_part_rri->ri_FdwRoutine->PlanForeignModify(root,
+ plan,
+ 1,
+ 0);
+ fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+ }
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(part_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(part_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_part_rri++;
+ i++;
+ }
+
+ cstate->partition_fdw_priv_lists = fdw_private_lists;
+ pfree(fdw_rte);
+ pfree(plan);
+ pfree(parse);
+ pfree(root);
+ }
}
else
{
@@ -1692,6 +1786,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1705,6 +1801,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);
}
@@ -2255,6 +2368,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2281,6 +2395,7 @@ CopyFrom(CopyState cstate)
* only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
{
@@ -2391,6 +2506,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2418,6 +2534,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;
@@ -2439,10 +2556,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 */
@@ -2494,6 +2647,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 */
@@ -2523,7 +2701,16 @@ CopyFrom(CopyState cstate)
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)
@@ -2553,7 +2740,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,
@@ -2577,6 +2765,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2590,7 +2784,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 3b72ae3..6478211 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1293,6 +1293,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 ea3f59a..b5125a8 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,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck =
- RelationGetPartitionQual(resultRelationDesc, true);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck =
+ RelationGetPartitionQual(resultRelationDesc,
+ true);
}
/*
@@ -1316,6 +1320,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);
@@ -2996,3 +3001,43 @@ 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;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+
+ econtext->ecxt_scantuple = slot;
+ 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 a612b08..44801b8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -272,6 +273,45 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ if (mtstate->mt_partition_tree_root)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ /*
+ * Get the index of leaf partitions that we'll use to get the correct
+ * ResultRelInfo from mtstate->mt_partitions[].
+ */
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_tree_root,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ /*
+ * Save the old ResultRelInfo and switch to the one corresponding to
+ * the selected partition.
+ */
+ saved_resultRelInfo = resultRelInfo;
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+
+ /* For ExecInsertIndexTuples() to work on the partition's indexes */
+ estate->es_result_relation_info = resultRelInfo;
+
+ /*
+ * We might need to convert from the parent rowtype to the partition
+ * rowtype.
+ */
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ {
+ tuple = do_convert_tuple(tuple, map);
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ }
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -511,6 +551,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
@@ -1565,6 +1611,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)));
@@ -1655,6 +1702,98 @@ 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_TABLE)
+ {
+ int i,
+ j,
+ num_leaf_parts;
+ List *leaf_parts;
+ ListCell *cell;
+ ResultRelInfo *leaf_part_rri;
+
+ mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel);
+ leaf_parts = get_leaf_partition_oids(mtstate->mt_partition_tree_root);
+ num_leaf_parts = list_length(leaf_parts);
+
+ 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_part_rri = mtstate->mt_partitions;
+ i = j = 0;
+ foreach(cell, leaf_parts)
+ {
+ Oid ftoid = lfirst_oid(cell);
+ Relation part_rel;
+
+ part_rel = heap_open(ftoid, RowExclusiveLock);
+
+ /*
+ * Verify result relation is a valid target for the current
+ * operation
+ */
+ CheckValidResultRel(part_rel, CMD_INSERT);
+
+ InitResultRelInfo(leaf_part_rri,
+ part_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_part_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_part_rri, false);
+
+ if (leaf_part_rri->ri_FdwRoutine)
+ {
+ ListCell *lc;
+ List *fdw_private;
+ int k;
+
+ /*
+ * There are as many fdw_private's in fdwPrivLists as there
+ * are FDW partitions, but we must find the intended for the
+ * this foreign table.
+ */
+ k = 0;
+ foreach(lc, node->fdwPartitionOids)
+ {
+ if (lfirst_oid(lc) == ftoid)
+ break;
+ k++;
+ }
+
+ Assert(k < num_leaf_parts);
+ fdw_private = (List *) list_nth(node->fdwPrivLists, k);
+ Assert(fdw_private != NIL);
+
+ leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+ leaf_part_rri,
+ fdw_private,
+ 0,
+ eflags);
+ j++;
+ }
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(part_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(part_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_part_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1972,6 +2111,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 28d0036..470dee7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);
+ COPY_NODE_FIELD(fdwPartitionOids);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d858f5..5c8eced 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -340,6 +340,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
+ WRITE_NODE_FIELD(fdwPartitionOids);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c587d4e..ddd78c7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1495,6 +1495,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);
+ READ_NODE_FIELD(fdwPartitionOids);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ad49674..29f40fe 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_inherits_fn.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/extensible.h"
@@ -6159,6 +6160,82 @@ 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,
+ *fdw_partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ return node;
+
+ partition_oids = find_all_inheritors(rte->relid, NoLock, NULL);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+ fdw_partition_oids = NIL;
+
+ /*
+ * To force the FDW driver fetch the intended RTE, we need to temporarily
+ * switch root->simple_rte_array to the one holding only that RTE at a
+ * designated index, for every foreign table.
+ */
+ 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;
+
+ /*
+ * We are only interested in foreign tables. Note that this will
+ * also eliminate any partitioned tables since foreign tables can
+ * only ever be leaf partitions.
+ */
+ if (!oid_is_foreign_table(myoid))
+ continue;
+
+ fdw_partition_oids = lappend_oid(fdw_partition_oids, myoid);
+
+ 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(), also,
+ * see the above comment.
+ */
+ 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;
+ node->fdwPartitionOids = fdw_partition_oids;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index a2cbf14..e85ca0a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1715,3 +1715,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 6901e08..c10b6c3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -798,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 (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
+ 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 057b1e3..86cc598 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/rel.h"
@@ -50,4 +52,9 @@ extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel);
extern List *get_leaf_partition_oids(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 136276b..c62946f 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 ff8b66b..ce01008 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"
@@ -1147,6 +1148,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/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e2fbc7d..d82222c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -189,6 +189,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
+ List *fdwPartitionOids; /* OIDs of FDW-managed partition */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
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 9ae6b09..d5dcb59 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -227,6 +227,58 @@ 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)
+
+-- ok
+insert into list_parted values (null, 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_null | | 0
+ part_null | | 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
+(8 rows)
+
-- cleanup
drop table range_parted cascade;
NOTICE: drop cascades to 4 other objects
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index b6e821e..fbd30d9 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -140,6 +140,31 @@ 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;
+
+-- ok
+insert into list_parted values (null, 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
[text/x-diff] 0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-14.patch (24.7K, ../../56ec5504-c4d7-534c-8832-696dddfd58d1@lab.ntt.co.jp/9-0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel-14.patch)
download | inline diff:
From f252a19667618f395fc9617a7889ff79ad002f61 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 157512c..288989b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2771,7 +2771,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</>.
@@ -2793,12 +2793,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>
@@ -2842,59 +2845,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 >= 100 AND outletID < 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>
@@ -2911,8 +2877,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>
@@ -2940,7 +2908,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
@@ -2971,12 +2939,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 FROM ('2016-07-01') TO ('2016-08-01');
+CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES FROM ('2016-08-01') TO ('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 FROM ('2017-04-01') TO ('2017-05-01');
+CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES FROM ('2017-05-01') TO ('2017-06-01');
+CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES FROM ('2017-06-01') TO ('2017-07-01');
</programlisting>
Each of the partitions are complete tables in their own right,
@@ -2986,36 +2954,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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2006m03 (
- CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
-) INHERITS (measurement);
-...
-CREATE TABLE measurement_y2007m11 (
- CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2007m12 (
- CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
-) INHERITS (measurement);
-CREATE TABLE measurement_y2008m01 (
- CHECK ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3024,110 +2965,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 >= DATE '2006-02-01' AND
- NEW.logdate < DATE '2006-03-01' ) THEN
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
- ELSIF ( NEW.logdate >= DATE '2006-03-01' AND
- NEW.logdate < DATE '2006-04-01' ) THEN
- INSERT INTO measurement_y2006m03 VALUES (NEW.*);
- ...
- ELSIF ( NEW.logdate >= DATE '2008-01-01' AND
- NEW.logdate < 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">
@@ -3145,22 +2995,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
@@ -3175,9 +3020,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement;
were created above:
<programlisting>
-CREATE TABLE measurement_y2008m02 (
- CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
-) INHERITS (measurement);
+CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES FROM ('2017-07-01') TO ('2017-08-01');
</programlisting>
As an alternative, it is sometimes more convenient to create the
@@ -3186,13 +3029,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 >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
-\copy measurement_y2008m02 from 'measurement_y2008m02'
+ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07
+ CHECK ( logdate >= DATE '2017-07-01' AND logdate < 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 FROM ('2017-07-01') TO ('2017-08-01');
</programlisting>
</para>
</sect2>
@@ -3211,7 +3056,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
<programlisting>
SET constraint_exclusion = on;
-SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
+SELECT count(*) FROM measurement WHERE logdate >= DATE '2017-01-01';
</programlisting>
Without constraint exclusion, the above query would scan each of
@@ -3220,7 +3065,9 @@ SELECT count(*) FROM measurement WHERE logdate >= 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>
@@ -3230,23 +3077,23 @@ SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = off;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
-
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=158.66..158.68 rows=1 width=0)
- -> Append (cost=0.00..151.88 rows=2715 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=866.69..866.70 rows=1 width=8)
+ -> Append (cost=0.00..828.12 rows=15426 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
...
- -> Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+ -> Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2017-01-01'::date)
</programlisting>
Some or all of the partitions might use index scans instead of
@@ -3257,15 +3104,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
<programlisting>
SET constraint_exclusion = on;
-EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
- QUERY PLAN
------------------------------------------------------------------------------------------------
- Aggregate (cost=63.47..63.48 rows=1 width=0)
- -> Append (cost=0.00..60.75 rows=1086 width=0)
- -> Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
- -> Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0)
- Filter: (logdate >= '2008-01-01'::date)
+EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2018-07-01';
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Aggregate (cost=34.67..34.68 rows=1 width=8)
+ -> Append (cost=0.00..33.12 rows=618 width=0)
+ -> Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
+ -> Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0)
+ Filter: (logdate >= '2018-07-01'::date)
</programlisting>
</para>
@@ -3292,93 +3139,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= 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 >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
-DO INSTEAD
- INSERT INTO measurement_y2006m02 VALUES (NEW.*);
-...
-CREATE RULE measurement_insert_y2008m01 AS
-ON INSERT TO measurement WHERE
- ( logdate >= DATE '2008-01-01' AND logdate < 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>
@@ -3397,9 +3173,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>
@@ -3423,18 +3199,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
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-15 19:21 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-15 19:21 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 15, 2016 at 5:30 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/11/11 19:30, Amit Langote wrote:
>>
>> Attached updated patches.
>
> Here is the latest version of the patches with some fixes along with those
> mentioned below (mostly in 0003):
>
> - Fixed the logic to skip the attach partition validation scan such that
> it won't skip scanning a list partition *that doesn't accept NULLs* if
> the partition key column is not set NOT NULL (it similarly doesn't skip
> scanning a range partition if either of the partition key columns is not
> set NOT NULL, because a range partition key cannot contain NULLs at all)
>
> - Added some more regression tests for ATTACH PARTITION command
>
> - Some fixes to documentation and source code comments
Have you done any performance testing on the tuple routing code?
Suppose we insert a million (or 10 million) tuples into an
unpartitioned table, a table with 10 partitions, a table with 100
partitions, a table with 1000 partitions, and a table that is
partitioned into 10 partitions each of which has 10 subpartitions.
Ideally, the partitioned cases would run almost as fast as the
unpartitioned case, but probably there will be some overhead.
However, it would be useful to know how much. Also, it would be
useful to set up the same cases with inheritance using a PL/pgsql ON
INSERT trigger for tuple routing and compare. Hopefully the tuple
routing code is far faster than a trigger, but we should make sure
that's the case and look for optimizations if not. Also, it would be
useful to know how much slower the tuple-mapping-required case is than
the no-tuple-mapping-required case.
I think the comments in some of the later patches could use some work
yet. For example, in 0007, FormPartitionKeyDatum()'s header comment
is largely uninformative, get_partition_for_tuple()'s header comment
doesn't explain what the return value means in the non-failure case,
and ExecFindPartition() doesn't have a header comment at all.
The number of places in these patches where you end up reopening a
hopefully-already-locked relation with NoLock (or sometimes with
AccessShareLock) is worrying to me. I think that's a coding pattern
we should be seeking to avoid; every one of those is not only a hazard
(what if we reach that point in the code without a lock?) but a
possible performance issue (we have to look up the OID in the
backend-private hash table; and if you are passing AccessShareLock
then you might also hit the lock manager which could be slow or create
deadlock hazards). It would be much better to pass the Relation
around rather than the OID whenever possible.
Also, in 0006:
- I doubt that PartitionTreeNodeData's header comment will survive
contact with pgindent.
In 0007:
- oid_is_foreign_table could/should do a syscache lookup instead of
opening the heap relation. But actually I think you could drop it
altogether and use get_rel_relkind().
- The XXX in EndCopy will need to be fixed somehow.
- I suspect that many of the pfree's in this patch are pointless
because the contexts in which those allocations are performed will be
reset or deleted shortly afterwards anyway. Only pfree data that
might otherwise live substantially longer than we want, because pfree
consumes some cycles.
- The code in make_modifytable() to swap out the rte_array for a fake
one looks like an unacceptable kludge. I don't know offhand what a
better design would look like, but what you've got is really ugly.
- I don't understand how it can be right for get_partition_for_tuple()
to be emitting an error that says "range partition key contains null".
A defective partition key should be detected at partition creation
time, not in the middle of tuple routing.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-17 16:43 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 2 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-17 16:43 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> OK, I will share the performance results soon.
Thanks.
>> Also, in 0006:
>>
>> - I doubt that PartitionTreeNodeData's header comment will survive
>> contact with pgindent.
>
> Fixed by adding "/* ----" at the top of the comment.
OK. Hopefully you also tested that. In general, with a patch set
this large, the more you can do to make it pgindent-clean, the better.
Ideally none of your code would get changed by pgindent, or at least
not the new files. But note that you will probably have have to
update typedefs.list if you actually want to be able to run it without
having it mangle things that involve your new structs.
>> - The code in make_modifytable() to swap out the rte_array for a fake
>> one looks like an unacceptable kludge. I don't know offhand what a
>> better design would look like, but what you've got is really ugly.
>
> Agree that it looks horrible. The problem is we don't add partition
> (child table) RTEs when planning an insert on the parent and FDW
> partitions can't do without some planner handling - planForeignModify()
> expects a valid PlannerInfo for deparsing target lists (basically, to be
> able to use planner_rt_fetch()).
>
> Any design beside this kludge would perhaps mean that we will be adding
> valid partition RTEs at some earlier planning stage much like what
> expand_inherited_rtentry() does during non-insert queries. Perhaps, we
> could have expand_inherited_rtentry() make an exception in partitioned
> tables' case and create root->append_rel_list members even in the insert
> case. We could then go over the append_rel_list in make_modifytable() to
> get the valid RT index of foreign child tables to pass to
> PlanForeignModify(). Using append_rel_list for insert planning is perhaps
> equally ugly though. Thoughts?
If it's only needed for foreign tables, how about for v1 we just throw
an error and say errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route inserted tuples to a foreign table") for now. We
can come back and fix it later. Doing more inheritance expansion
early is probably not a good idea because it likely sucks for
performance, and that's especially unfortunate if it's only needed for
foreign tables. Coming up with some new FDW API or some modification
to the existing one is probably better, but I don't really want to get
hung up on that right now.
>> - I don't understand how it can be right for get_partition_for_tuple()
>> to be emitting an error that says "range partition key contains null".
>> A defective partition key should be detected at partition creation
>> time, not in the middle of tuple routing.
>
> That error is emitted when the partition key of the *input tuple* is found
> to contain NULLs. Maybe we need to consider something like how btree
> indexes treat NULLs - order NULLs either before or after all the non-NULL
> values based on NULLS FIRST/LAST config. Thoughts?
Well, I'd say my confusion suggests that the error message needs to be
clearer about what the problem is. I think this is basically a case
of there being no partition for the given row, so maybe just arrange
to use that same message here ("no partition of relation \"%s\" found
for row").
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-17 19:14 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-17 19:14 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Meanwhile, here are updated patch that address most of the following comments.
OK, I have re-reviewed 0005 and it looks basically fine to me, modulo
a few minor nitpicks. "This is called, *iff*" shouldn't have a comma
there, and I think the entire comment block that starts with "NOTE:
SQL specifies that a NULL" and ends with "it's unlikely that NULL
would result." should be changed to say something like /* As for
catalogued constraints, we treat a NULL result as a success, not a
failure. */ rather than duplicating an existing comment that doesn't
quite apply here. Finally, ExecConstraints() contains a new if-block
whose sole contents are another if-block. Perhaps if (this && that)
would be better.
Regarding 0006 and 0007, I think the PartitionTreeNodeData structure
you've chosen is awfully convoluted and probably not that efficient.
For example, get_partition_for_tuple() contains this loop:
+ prev = parent;
+ node = parent->downlink;
+ while (node != NULL)
+ {
+ if (node->index >= cur_idx)
+ break;
+
+ prev = node;
+ node = node->next;
+ }
Well, it looks to me like that's an O(n) way to find the n'th
partition, which seems like a pretty bad idea in performance-critical
code, which this is. I think this whole structure needs a pretty
heavy overhaul. Here's a proposal:
1. Forget the idea of a tree. Instead, let the total number of tables
in the partitioning hierarchy be N and let the number of those that
are partitioned be K. Assign each partitioned table in the hierarchy
an index between 0 and K-1. Make your top level data structure (in
lieu of PartitionTreeNodeData) be an array of K PartitionDispatch
objects, with the partitioning root in entry 0 and the rest in the
remaining entries.
2. Within each PartitionDispatch object, store (a) a pointer to a
PartitionDesc and (b) an array of integers of length equal to the
PartitionDesc's nparts value. Each integer i, if non-negative, is the
final return value for get_partition_for_tuple. If i == -1, tuple
routing fails. If i < -1, we must next route using the subpartition
whose PartitionDesc is at index -(i+1). Arrange for the array to be
in the same order the PartitionDesc's OID list.
3. Now get_partition_for_tuple looks something like this:
K = 0
loop:
pd = PartitionDispatch[K]
idx = list/range_partition_for_tuple(pd->partdesc, ...)
if (idx >= -1)
return idx
K = -(idx + 1)
No recursion, minimal pointer chasing, no linked lists. The whole
thing is basically trivial aside from the cost of
list/range_partition_for_tuple itself; optimizing that is a different
project. I might have some details slightly off here, but hopefully
you can see what I'm going for: you want to keep the computation that
happens in get_partition_for_tuple() to an absolute minimum, and
instead set things up in advance so that getting the partition for a
tuple is FAST. And you want the data structures that you are using in
that process to be very compact, hence arrays instead of linked lists.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-18 01:18 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-18 01:18 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/18 1:43, Robert Haas wrote:
> On Thu, Nov 17, 2016 at 6:27 AM, Amit Langote wrote:
>> OK, I will share the performance results soon.
>
> Thanks.
>
>>> Also, in 0006:
>>>
>>> - I doubt that PartitionTreeNodeData's header comment will survive
>>> contact with pgindent.
>>
>> Fixed by adding "/* ----" at the top of the comment.
>
> OK. Hopefully you also tested that. In general, with a patch set
> this large, the more you can do to make it pgindent-clean, the better.
> Ideally none of your code would get changed by pgindent, or at least
> not the new files. But note that you will probably have have to
> update typedefs.list if you actually want to be able to run it without
> having it mangle things that involve your new structs.
OK, I was afraid that running pgindent might end up introducing unrelated
diffs in the patch, but I'm sure any code committed to HEAD would have
been through pgindent and my worry might be pointless after all. Also,
thanks for the tip about the typedefs.list. I will go try pgindent'ing
the patches.
>>> - The code in make_modifytable() to swap out the rte_array for a fake
>>> one looks like an unacceptable kludge. I don't know offhand what a
>>> better design would look like, but what you've got is really ugly.
>>
>> Agree that it looks horrible. The problem is we don't add partition
>> (child table) RTEs when planning an insert on the parent and FDW
>> partitions can't do without some planner handling - planForeignModify()
>> expects a valid PlannerInfo for deparsing target lists (basically, to be
>> able to use planner_rt_fetch()).
>>
>> Any design beside this kludge would perhaps mean that we will be adding
>> valid partition RTEs at some earlier planning stage much like what
>> expand_inherited_rtentry() does during non-insert queries. Perhaps, we
>> could have expand_inherited_rtentry() make an exception in partitioned
>> tables' case and create root->append_rel_list members even in the insert
>> case. We could then go over the append_rel_list in make_modifytable() to
>> get the valid RT index of foreign child tables to pass to
>> PlanForeignModify(). Using append_rel_list for insert planning is perhaps
>> equally ugly though. Thoughts?
>
> If it's only needed for foreign tables, how about for v1 we just throw
> an error and say errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> errmsg("cannot route inserted tuples to a foreign table") for now. We
> can come back and fix it later. Doing more inheritance expansion
> early is probably not a good idea because it likely sucks for
> performance, and that's especially unfortunate if it's only needed for
> foreign tables. Coming up with some new FDW API or some modification
> to the existing one is probably better, but I don't really want to get
> hung up on that right now.
OK, I agree with the decision to make tuple-routing a unsupported feature
in the first cut as far as foreign partitions are concerned. Once can
still insert data into partitions directly.
I am assuming that the error should be thrown all the way below
ExecInsert(), not in the planner. Which means I should revert any changes
I've made to the planner in this patch.
>>> - I don't understand how it can be right for get_partition_for_tuple()
>>> to be emitting an error that says "range partition key contains null".
>>> A defective partition key should be detected at partition creation
>>> time, not in the middle of tuple routing.
>>
>> That error is emitted when the partition key of the *input tuple* is found
>> to contain NULLs. Maybe we need to consider something like how btree
>> indexes treat NULLs - order NULLs either before or after all the non-NULL
>> values based on NULLS FIRST/LAST config. Thoughts?
>
> Well, I'd say my confusion suggests that the error message needs to be
> clearer about what the problem is. I think this is basically a case
> of there being no partition for the given row, so maybe just arrange
> to use that same message here ("no partition of relation \"%s\" found
> for row").
The reason NULLs in an input row are caught and rejected (with the current
message) before control reaches range_partition_for_tuple() is because
it's not clear to me whether the range bound comparison logic in
partition_rbound_datum_cmp() should be prepared to handle NULLs and what
the results of comparisons should look like. Currently, all it ever
expects to see in the input tuple's partition key is non-NULL datums.
Comparison proceeds as follows: if a range bound datum is a finite value,
we invoke the comparison proc or if it is infinite, we conclude that the
input tuple is > or < the bound in question based on whether the bound is
a lower or upper bound, respectively.
Or are you saying that get_tuple_for_partition() should simply return -1
(partition not found) in case of encountering a NULL in range partition
key to the caller instead of throwing error as is now? If the user sees
the message and decides to create a new range partition that *will* accept
such a row, how do they decide what its boundaries should be?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-18 16:39 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-18 16:39 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Nov 17, 2016 at 8:18 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> The reason NULLs in an input row are caught and rejected (with the current
> message) before control reaches range_partition_for_tuple() is because
> it's not clear to me whether the range bound comparison logic in
> partition_rbound_datum_cmp() should be prepared to handle NULLs and what
> the results of comparisons should look like. Currently, all it ever
> expects to see in the input tuple's partition key is non-NULL datums.
> Comparison proceeds as follows: if a range bound datum is a finite value,
> we invoke the comparison proc or if it is infinite, we conclude that the
> input tuple is > or < the bound in question based on whether the bound is
> a lower or upper bound, respectively.
>
> Or are you saying that get_tuple_for_partition() should simply return -1
> (partition not found) in case of encountering a NULL in range partition
> key to the caller instead of throwing error as is now? If the user sees
> the message and decides to create a new range partition that *will* accept
> such a row, how do they decide what its boundaries should be?
Well, I think the first thing you have to decide is whether range
partitioning is going to support NULL values in the partition key
columns at all. If you want it to support that, then you've got to
decide how it's going to be specified in the SQL syntax. I had the
impression that you were planning not to support that, in which case
you need to reject all such rows.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-18 17:23 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-18 17:23 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Oh but wait, that means I can insert rows with NULLs in the range
> partition key if I choose to insert it directly into the partition,
> whereas I have been thinking all this while that there could never be
> NULLs in the partition key of a range partition. What's more,
> get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
> every partition key column in case of a range partition. Is that
> wrongheaded altogether? (also see my reply to your earlier message about
> NULLs in the range partition key)
The easiest thing to do might be to just enforce that all of the
partition key columns have to be not-null when the range-partitioned
table is defined, and reject any attempt to DROP NOT NULL on them
later. That's probably better that shoehorning it into the table
constraint.
> Thanks for the idea below!
>
>> 1. Forget the idea of a tree. Instead, let the total number of tables
>> in the partitioning hierarchy be N and let the number of those that
>> are partitioned be K. Assign each partitioned table in the hierarchy
>> an index between 0 and K-1. Make your top level data structure (in
>> lieu of PartitionTreeNodeData) be an array of K PartitionDispatch
>> objects, with the partitioning root in entry 0 and the rest in the
>> remaining entries.
>>
>> 2. Within each PartitionDispatch object, store (a) a pointer to a
>> PartitionDesc and (b) an array of integers of length equal to the
>> PartitionDesc's nparts value. Each integer i, if non-negative, is the
>> final return value for get_partition_for_tuple. If i == -1, tuple
>> routing fails. If i < -1, we must next route using the subpartition
>> whose PartitionDesc is at index -(i+1). Arrange for the array to be
>> in the same order the PartitionDesc's OID list.
>>
>> 3. Now get_partition_for_tuple looks something like this:
>>
>> K = 0
>> loop:
>> pd = PartitionDispatch[K]
>> idx = list/range_partition_for_tuple(pd->partdesc, ...)
>> if (idx >= -1)
>> return idx
>> K = -(idx + 1)
>>
>> No recursion, minimal pointer chasing, no linked lists. The whole
>> thing is basically trivial aside from the cost of
>> list/range_partition_for_tuple itself; optimizing that is a different
>> project. I might have some details slightly off here, but hopefully
>> you can see what I'm going for: you want to keep the computation that
>> happens in get_partition_for_tuple() to an absolute minimum, and
>> instead set things up in advance so that getting the partition for a
>> tuple is FAST. And you want the data structures that you are using in
>> that process to be very compact, hence arrays instead of linked lists.
>
> This sounds *much* better. Here is a quick attempt at coding the design
> you have outlined above in the attached latest set of patches.
That shrank both 0006 and 0007 substantially, and it should be faster,
too. I bet you can shrink them further:
- Why is PartitionKeyExecInfo a separate structure and why does it
have a NodeTag? I bet you can dump the node tag, merge it into
PartitionDispatch, and save some more code and some more
pointer-chasing.
- I still think it's a seriously bad idea for list partitioning and
range partitioning to need different code-paths all over the place
here. List partitions support nulls but not multi-column partitioning
keys and range partitions support multi-column partitioning keys but
not nulls, but you could use an internal structure that supports both.
Then you wouldn't need partition_list_values_bsearch and also
partition_rbound_bsearch; you could have one kind of bound structure
that can be bsearch'd for either list or range. You might even be
able to unify list_partition_for_tuple and range_partition_for_tuple
although that looks a little harder. In either case, you bsearch for
the greatest value <= the value you have. The only difference is that
for list partitioning, you have to enforce at the end that it is an
equal value, whereas for range partitioning less-than-or-equal-to is
enough. But you should still be able to arrange for more code
sharing.
- I don't see why you need the bound->lower stuff any more. If
rangeinfo.bounds[offset] is a lower bound for a partition, then
rangeinfo.bounds[offset+1] is either (a) the upper bound for that
partition and the partition is followed by a "gap" or (b) both the
upper bound for that partition and the lower bound for the next
partition. With the inclusive/exclusive bound stuff gone, every range
bound has the same sense: if the probed value is <= the bound then
we're supposed to be a lower-numbered partition, but if > then we're
supposed to be in this partition or a higher-numbered one.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-22 13:11 Rushabh Lathia <rushabh.lathia@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Rushabh Lathia @ 2016-11-22 13:11 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
Hi Amit,
I was just reading through your patches and here are some quick review
comments
for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch.
Review comments for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch:
1)
@@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind ==
RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind ==
RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind ==
RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
You should add the RELKIND_PARTITIONED_TABLE at the end of if condition that
will make diff minimal. While reading through the patch I noticed that there
is inconsistency - someplace its been added at the end and at few places its
at the start. I think you can make add it at the end of condition and be
consistent with each place.
2)
+ /*
+ * We need to transform the raw parsetrees corresponding to
partition
+ * expressions into executable expression trees. Like column
defaults
+ * and CHECK constraints, we could not have done the transformation
+ * earlier.
+ */
Additional space before "Like column defaults".
3)
- char relkind;
+ char relkind,
+ expected_relkind;
Newly added variable should be define separately with its type. Something
like:
char relkind;
+ char expected_relkind;
4)
a)
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
need alignment for last line.
b)
+ atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does
not exist",
+ pelem->name)));
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in
partition key",
+ pelem->name)));
need alignment for last line of ereport
c)
+ /* Disallow ROW triggers on partitioned tables */
+ if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables cannot have ROW triggers.")));
need alignment
5)
@@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt
*stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
I think adding bracket will look code more clear.
+ cxt.ispartitioned = (rel->rd_rel->relkind ==
RELKIND_PARTITIONED_TABLE);
6)
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Partitioning key data is stored in CacheMemoryContext to ensure it
survives
+ * as long as the relcache. To avoid leaking memory in that context in
case
+ * of an error partway through this function, we build the structure in the
+ * working context (which must be short-lived) and copy the completed
+ * structure into the cache memory.
extra space before "To avoid leaking memory"
7)
+ /* variable-length fields start here, but we allow direct access to
partattrs */
+ int2vector partattrs; /* attribute numbers of columns in
the
Why partattrs is allow direct access - its not really clear from the
comments.
I will continue reading more patch and testing functionality.. will share
the
comments as I have it.
Thanks,
On Tue, Nov 22, 2016 at 2:45 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Updated patches attached. I merged what used to be 0006 and 0007 into one.
>
> On 2016/11/19 2:23, Robert Haas wrote:
> > On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> >> Oh but wait, that means I can insert rows with NULLs in the range
> >> partition key if I choose to insert it directly into the partition,
> >> whereas I have been thinking all this while that there could never be
> >> NULLs in the partition key of a range partition. What's more,
> >> get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
> >> every partition key column in case of a range partition. Is that
> >> wrongheaded altogether? (also see my reply to your earlier message
> about
> >> NULLs in the range partition key)
> >
> > The easiest thing to do might be to just enforce that all of the
> > partition key columns have to be not-null when the range-partitioned
> > table is defined, and reject any attempt to DROP NOT NULL on them
> > later. That's probably better that shoehorning it into the table
> > constraint.
>
> Agreed that forcing range partitioning columns to be NOT NULL during table
> creation would be a better approach. But then we would have to reject
> using expressions in the range partition key, right?
>
> >> Thanks for the idea below!
> >>
> >>> 1. Forget the idea of a tree. Instead, let the total number of tables
> >>> in the partitioning hierarchy be N and let the number of those that
>
> [ ... ]
>
> >>>
> >>> No recursion, minimal pointer chasing, no linked lists. The whole
> >>> thing is basically trivial aside from the cost of
> >>> list/range_partition_for_tuple itself; optimizing that is a different
> >>> project. I might have some details slightly off here, but hopefully
> >>> you can see what I'm going for: you want to keep the computation that
> >>> happens in get_partition_for_tuple() to an absolute minimum, and
> >>> instead set things up in advance so that getting the partition for a
> >>> tuple is FAST. And you want the data structures that you are using in
> >>> that process to be very compact, hence arrays instead of linked lists.
> >>
> >> This sounds *much* better. Here is a quick attempt at coding the design
> >> you have outlined above in the attached latest set of patches.
> >
> > That shrank both 0006 and 0007 substantially, and it should be faster,
> > too. I bet you can shrink them further:
>
> Some changes described below have reduced the size to a certain degree.
>
> >
> > - Why is PartitionKeyExecInfo a separate structure and why does it
> > have a NodeTag? I bet you can dump the node tag, merge it into
> > PartitionDispatch, and save some more code and some more
> > pointer-chasing.
>
> OK, I merged the fields of what used to be PartitionKeyExecInfo into
> PartitionDispatchData as the latter's new fields key and keystate.
>
> > - I still think it's a seriously bad idea for list partitioning and
> > range partitioning to need different code-paths all over the place
> > here. List partitions support nulls but not multi-column partitioning
> > keys and range partitions support multi-column partitioning keys but
> > not nulls, but you could use an internal structure that supports both.
> > Then you wouldn't need partition_list_values_bsearch and also
> > partition_rbound_bsearch; you could have one kind of bound structure
> > that can be bsearch'd for either list or range. You might even be
> > able to unify list_partition_for_tuple and range_partition_for_tuple
> > although that looks a little harder. In either case, you bsearch for
> > the greatest value <= the value you have. The only difference is that
> > for list partitioning, you have to enforce at the end that it is an
> > equal value, whereas for range partitioning less-than-or-equal-to is
> > enough. But you should still be able to arrange for more code
> > sharing.
>
> I have considered these suggestions in the latest patch. Now instead of
> PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
> there is only one PartitionBoundInfo which consolidates the partition
> bound information of a partitioned table. Some of the fields are
> applicable only to one of list or range case; for example, null-accepting
> list partition index, infinite status of individual range datums.
>
> Also, there is now only one binary search function named
> partition_bound_bsearch() which invokes a comparison function named
> partition_bound_cmp(). The former searches a probe (a partition bound or
> tuple) within a PartitionBoundInfo, which is passed all the way down to
> the comparison function.
>
> Also, we no longer have list_partition_for_tuple() and
> range_partition_for_tuple(). Instead, in get_partition_for_tuple()
> itself, there is a bsearch followed by list and range partitioning
> specific steps based on the returned offset.
>
> > - I don't see why you need the bound->lower stuff any more. If
> > rangeinfo.bounds[offset] is a lower bound for a partition, then
> > rangeinfo.bounds[offset+1] is either (a) the upper bound for that
> > partition and the partition is followed by a "gap" or (b) both the
> > upper bound for that partition and the lower bound for the next
> > partition. With the inclusive/exclusive bound stuff gone, every range
> > bound has the same sense: if the probed value is <= the bound then
> > we're supposed to be a lower-numbered partition, but if > then we're
> > supposed to be in this partition or a higher-numbered one.
>
> OK, I've managed to get rid of lower. At least it is no longer kept in
> the new relcache struct PartitionBoundInfo. It is still kept in
> PartitionRangeBound which is used to hold individual range bounds when
> sorting them (during relcache build). Comparisons invoked during the
> aforementioned sorting step still need to distinguish between lower and
> upper bounds (such that '1)' < '[1').
>
> Tuple-routing no longer needs to look at lower. In that case, what you
> described above applies.
>
> As a result, one change became necessary: to how we flag individual range
> bound datum as infinite or not. Previously, it was a regular Boolean
> value (either infinite or not) and to distinguish +infinity from
> -infinity, we looked at whether the bound is lower or upper (the lower
> flag). Now, instead, the variable holding the status of individual range
> bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
> RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
> a bool. Upon encountering an infinite range bound datum, whether it's
> negative or positive infinity derives the comparison result. Consider the
> following example:
>
> partition p1 from (1, unbounded) to (1, 1);
> partition p2 from (1, 1) to (1, 10);
> partition p3 from (1, 10) to (1, unbounded);
> partition p4 from (2, unbounded) to (2, 1);
> ... so on
>
> In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
> (1, +inf), so that tuple (1, 15) is assigned to the proper partition.
>
> Does this last thing sound reasonable?
>
> Thanks,
> Amit
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Rushabh Lathia
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-22 19:50 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-22 19:50 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 22, 2016 at 4:15 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> The easiest thing to do might be to just enforce that all of the
>> partition key columns have to be not-null when the range-partitioned
>> table is defined, and reject any attempt to DROP NOT NULL on them
>> later. That's probably better that shoehorning it into the table
>> constraint.
>
> Agreed that forcing range partitioning columns to be NOT NULL during table
> creation would be a better approach. But then we would have to reject
> using expressions in the range partition key, right?
Why?
> As a result, one change became necessary: to how we flag individual range
> bound datum as infinite or not. Previously, it was a regular Boolean
> value (either infinite or not) and to distinguish +infinity from
> -infinity, we looked at whether the bound is lower or upper (the lower
> flag). Now, instead, the variable holding the status of individual range
> bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
> RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
> a bool.
You better not be using a bool to represent a ternary value! Use an
enum for that -- or if in the system catalogs, a char.
> Upon encountering an infinite range bound datum, whether it's
> negative or positive infinity derives the comparison result. Consider the
> following example:
>
> partition p1 from (1, unbounded) to (1, 1);
> partition p2 from (1, 1) to (1, 10);
> partition p3 from (1, 10) to (1, unbounded);
> partition p4 from (2, unbounded) to (2, 1);
> ... so on
>
> In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
> (1, +inf), so that tuple (1, 15) is assigned to the proper partition.
Right.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 05:35 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 2 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-24 05:35 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
I am trying to create a partitioned table with primary keys on the
partitions. Here's the corresponding syntax as per documentation
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name WITH OPTIONS [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] partition_bound_spec
IIUC, it should allow "create table t1_p1 partition of t1 (a primary
key) ...", (a primary key) is nothing but "column_name
column_constraint", but here's what happens
create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
ERROR: syntax error at or near "primary"
LINE 1: create table t1_p1 partition of t1 (a primary key) for value...
The same syntax also suggests using table_constraints but that too doesn't work
create table t1_p1 partition of t1 (primary key (a) ) for values
from (0) to (100);
ERROR: inherited relation "t1" is not a table or foreign table
of course t1 is a table, what it isn't?
Am I missing something? How do I define constraints on the partitions?
On Tue, Nov 22, 2016 at 2:45 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> Updated patches attached. I merged what used to be 0006 and 0007 into one.
>
> On 2016/11/19 2:23, Robert Haas wrote:
>> On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> Oh but wait, that means I can insert rows with NULLs in the range
>>> partition key if I choose to insert it directly into the partition,
>>> whereas I have been thinking all this while that there could never be
>>> NULLs in the partition key of a range partition. What's more,
>>> get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
>>> every partition key column in case of a range partition. Is that
>>> wrongheaded altogether? (also see my reply to your earlier message about
>>> NULLs in the range partition key)
>>
>> The easiest thing to do might be to just enforce that all of the
>> partition key columns have to be not-null when the range-partitioned
>> table is defined, and reject any attempt to DROP NOT NULL on them
>> later. That's probably better that shoehorning it into the table
>> constraint.
>
> Agreed that forcing range partitioning columns to be NOT NULL during table
> creation would be a better approach. But then we would have to reject
> using expressions in the range partition key, right?
>
>>> Thanks for the idea below!
>>>
>>>> 1. Forget the idea of a tree. Instead, let the total number of tables
>>>> in the partitioning hierarchy be N and let the number of those that
>
> [ ... ]
>
>>>>
>>>> No recursion, minimal pointer chasing, no linked lists. The whole
>>>> thing is basically trivial aside from the cost of
>>>> list/range_partition_for_tuple itself; optimizing that is a different
>>>> project. I might have some details slightly off here, but hopefully
>>>> you can see what I'm going for: you want to keep the computation that
>>>> happens in get_partition_for_tuple() to an absolute minimum, and
>>>> instead set things up in advance so that getting the partition for a
>>>> tuple is FAST. And you want the data structures that you are using in
>>>> that process to be very compact, hence arrays instead of linked lists.
>>>
>>> This sounds *much* better. Here is a quick attempt at coding the design
>>> you have outlined above in the attached latest set of patches.
>>
>> That shrank both 0006 and 0007 substantially, and it should be faster,
>> too. I bet you can shrink them further:
>
> Some changes described below have reduced the size to a certain degree.
>
>>
>> - Why is PartitionKeyExecInfo a separate structure and why does it
>> have a NodeTag? I bet you can dump the node tag, merge it into
>> PartitionDispatch, and save some more code and some more
>> pointer-chasing.
>
> OK, I merged the fields of what used to be PartitionKeyExecInfo into
> PartitionDispatchData as the latter's new fields key and keystate.
>
>> - I still think it's a seriously bad idea for list partitioning and
>> range partitioning to need different code-paths all over the place
>> here. List partitions support nulls but not multi-column partitioning
>> keys and range partitions support multi-column partitioning keys but
>> not nulls, but you could use an internal structure that supports both.
>> Then you wouldn't need partition_list_values_bsearch and also
>> partition_rbound_bsearch; you could have one kind of bound structure
>> that can be bsearch'd for either list or range. You might even be
>> able to unify list_partition_for_tuple and range_partition_for_tuple
>> although that looks a little harder. In either case, you bsearch for
>> the greatest value <= the value you have. The only difference is that
>> for list partitioning, you have to enforce at the end that it is an
>> equal value, whereas for range partitioning less-than-or-equal-to is
>> enough. But you should still be able to arrange for more code
>> sharing.
>
> I have considered these suggestions in the latest patch. Now instead of
> PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
> there is only one PartitionBoundInfo which consolidates the partition
> bound information of a partitioned table. Some of the fields are
> applicable only to one of list or range case; for example, null-accepting
> list partition index, infinite status of individual range datums.
>
> Also, there is now only one binary search function named
> partition_bound_bsearch() which invokes a comparison function named
> partition_bound_cmp(). The former searches a probe (a partition bound or
> tuple) within a PartitionBoundInfo, which is passed all the way down to
> the comparison function.
>
> Also, we no longer have list_partition_for_tuple() and
> range_partition_for_tuple(). Instead, in get_partition_for_tuple()
> itself, there is a bsearch followed by list and range partitioning
> specific steps based on the returned offset.
>
>> - I don't see why you need the bound->lower stuff any more. If
>> rangeinfo.bounds[offset] is a lower bound for a partition, then
>> rangeinfo.bounds[offset+1] is either (a) the upper bound for that
>> partition and the partition is followed by a "gap" or (b) both the
>> upper bound for that partition and the lower bound for the next
>> partition. With the inclusive/exclusive bound stuff gone, every range
>> bound has the same sense: if the probed value is <= the bound then
>> we're supposed to be a lower-numbered partition, but if > then we're
>> supposed to be in this partition or a higher-numbered one.
>
> OK, I've managed to get rid of lower. At least it is no longer kept in
> the new relcache struct PartitionBoundInfo. It is still kept in
> PartitionRangeBound which is used to hold individual range bounds when
> sorting them (during relcache build). Comparisons invoked during the
> aforementioned sorting step still need to distinguish between lower and
> upper bounds (such that '1)' < '[1').
>
> Tuple-routing no longer needs to look at lower. In that case, what you
> described above applies.
>
> As a result, one change became necessary: to how we flag individual range
> bound datum as infinite or not. Previously, it was a regular Boolean
> value (either infinite or not) and to distinguish +infinity from
> -infinity, we looked at whether the bound is lower or upper (the lower
> flag). Now, instead, the variable holding the status of individual range
> bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
> RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
> a bool. Upon encountering an infinite range bound datum, whether it's
> negative or positive infinity derives the comparison result. Consider the
> following example:
>
> partition p1 from (1, unbounded) to (1, 1);
> partition p2 from (1, 1) to (1, 10);
> partition p3 from (1, 10) to (1, unbounded);
> partition p4 from (2, unbounded) to (2, 1);
> ... so on
>
> In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
> (1, +inf), so that tuple (1, 15) is assigned to the proper partition.
>
> Does this last thing sound reasonable?
>
> Thanks,
> Amit
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 05:39 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 0 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-24 05:39 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
Unsurprisingly ALTER TABLE worked
alter table t1_p1 add primary key(a);
ALTER TABLE
postgres=# \d+ t1_p1
Table "public.t1_p1"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | not null | | plain | |
b | integer | | | | plain | |
Indexes:
"t1_p1_pkey" PRIMARY KEY, btree (a)
Inherits: t1
On Thu, Nov 24, 2016 at 11:05 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> I am trying to create a partitioned table with primary keys on the
> partitions. Here's the corresponding syntax as per documentation
> CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
> IF NOT EXISTS ] table_name
> PARTITION OF parent_table [ (
> { column_name WITH OPTIONS [ column_constraint [ ... ] ]
> | table_constraint }
> [, ... ]
> ) ] partition_bound_spec
>
> IIUC, it should allow "create table t1_p1 partition of t1 (a primary
> key) ...", (a primary key) is nothing but "column_name
> column_constraint", but here's what happens
> create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
> ERROR: syntax error at or near "primary"
> LINE 1: create table t1_p1 partition of t1 (a primary key) for value...
>
> The same syntax also suggests using table_constraints but that too doesn't work
> create table t1_p1 partition of t1 (primary key (a) ) for values
> from (0) to (100);
> ERROR: inherited relation "t1" is not a table or foreign table
>
> of course t1 is a table, what it isn't?
>
> Am I missing something? How do I define constraints on the partitions?
>
>
> On Tue, Nov 22, 2016 at 2:45 PM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>
>> Updated patches attached. I merged what used to be 0006 and 0007 into one.
>>
>> On 2016/11/19 2:23, Robert Haas wrote:
>>> On Fri, Nov 18, 2016 at 5:59 AM, Amit Langote
>>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>> Oh but wait, that means I can insert rows with NULLs in the range
>>>> partition key if I choose to insert it directly into the partition,
>>>> whereas I have been thinking all this while that there could never be
>>>> NULLs in the partition key of a range partition. What's more,
>>>> get_qual_for_partbound() (patch 0003) emits a IS NOT NULL constraint for
>>>> every partition key column in case of a range partition. Is that
>>>> wrongheaded altogether? (also see my reply to your earlier message about
>>>> NULLs in the range partition key)
>>>
>>> The easiest thing to do might be to just enforce that all of the
>>> partition key columns have to be not-null when the range-partitioned
>>> table is defined, and reject any attempt to DROP NOT NULL on them
>>> later. That's probably better that shoehorning it into the table
>>> constraint.
>>
>> Agreed that forcing range partitioning columns to be NOT NULL during table
>> creation would be a better approach. But then we would have to reject
>> using expressions in the range partition key, right?
>>
>>>> Thanks for the idea below!
>>>>
>>>>> 1. Forget the idea of a tree. Instead, let the total number of tables
>>>>> in the partitioning hierarchy be N and let the number of those that
>>
>> [ ... ]
>>
>>>>>
>>>>> No recursion, minimal pointer chasing, no linked lists. The whole
>>>>> thing is basically trivial aside from the cost of
>>>>> list/range_partition_for_tuple itself; optimizing that is a different
>>>>> project. I might have some details slightly off here, but hopefully
>>>>> you can see what I'm going for: you want to keep the computation that
>>>>> happens in get_partition_for_tuple() to an absolute minimum, and
>>>>> instead set things up in advance so that getting the partition for a
>>>>> tuple is FAST. And you want the data structures that you are using in
>>>>> that process to be very compact, hence arrays instead of linked lists.
>>>>
>>>> This sounds *much* better. Here is a quick attempt at coding the design
>>>> you have outlined above in the attached latest set of patches.
>>>
>>> That shrank both 0006 and 0007 substantially, and it should be faster,
>>> too. I bet you can shrink them further:
>>
>> Some changes described below have reduced the size to a certain degree.
>>
>>>
>>> - Why is PartitionKeyExecInfo a separate structure and why does it
>>> have a NodeTag? I bet you can dump the node tag, merge it into
>>> PartitionDispatch, and save some more code and some more
>>> pointer-chasing.
>>
>> OK, I merged the fields of what used to be PartitionKeyExecInfo into
>> PartitionDispatchData as the latter's new fields key and keystate.
>>
>>> - I still think it's a seriously bad idea for list partitioning and
>>> range partitioning to need different code-paths all over the place
>>> here. List partitions support nulls but not multi-column partitioning
>>> keys and range partitions support multi-column partitioning keys but
>>> not nulls, but you could use an internal structure that supports both.
>>> Then you wouldn't need partition_list_values_bsearch and also
>>> partition_rbound_bsearch; you could have one kind of bound structure
>>> that can be bsearch'd for either list or range. You might even be
>>> able to unify list_partition_for_tuple and range_partition_for_tuple
>>> although that looks a little harder. In either case, you bsearch for
>>> the greatest value <= the value you have. The only difference is that
>>> for list partitioning, you have to enforce at the end that it is an
>>> equal value, whereas for range partitioning less-than-or-equal-to is
>>> enough. But you should still be able to arrange for more code
>>> sharing.
>>
>> I have considered these suggestions in the latest patch. Now instead of
>> PartitionListInfo, PartitionRangeInfo, and BoundCollectionData structs,
>> there is only one PartitionBoundInfo which consolidates the partition
>> bound information of a partitioned table. Some of the fields are
>> applicable only to one of list or range case; for example, null-accepting
>> list partition index, infinite status of individual range datums.
>>
>> Also, there is now only one binary search function named
>> partition_bound_bsearch() which invokes a comparison function named
>> partition_bound_cmp(). The former searches a probe (a partition bound or
>> tuple) within a PartitionBoundInfo, which is passed all the way down to
>> the comparison function.
>>
>> Also, we no longer have list_partition_for_tuple() and
>> range_partition_for_tuple(). Instead, in get_partition_for_tuple()
>> itself, there is a bsearch followed by list and range partitioning
>> specific steps based on the returned offset.
>>
>>> - I don't see why you need the bound->lower stuff any more. If
>>> rangeinfo.bounds[offset] is a lower bound for a partition, then
>>> rangeinfo.bounds[offset+1] is either (a) the upper bound for that
>>> partition and the partition is followed by a "gap" or (b) both the
>>> upper bound for that partition and the lower bound for the next
>>> partition. With the inclusive/exclusive bound stuff gone, every range
>>> bound has the same sense: if the probed value is <= the bound then
>>> we're supposed to be a lower-numbered partition, but if > then we're
>>> supposed to be in this partition or a higher-numbered one.
>>
>> OK, I've managed to get rid of lower. At least it is no longer kept in
>> the new relcache struct PartitionBoundInfo. It is still kept in
>> PartitionRangeBound which is used to hold individual range bounds when
>> sorting them (during relcache build). Comparisons invoked during the
>> aforementioned sorting step still need to distinguish between lower and
>> upper bounds (such that '1)' < '[1').
>>
>> Tuple-routing no longer needs to look at lower. In that case, what you
>> described above applies.
>>
>> As a result, one change became necessary: to how we flag individual range
>> bound datum as infinite or not. Previously, it was a regular Boolean
>> value (either infinite or not) and to distinguish +infinity from
>> -infinity, we looked at whether the bound is lower or upper (the lower
>> flag). Now, instead, the variable holding the status of individual range
>> bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
>> RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
>> a bool. Upon encountering an infinite range bound datum, whether it's
>> negative or positive infinity derives the comparison result. Consider the
>> following example:
>>
>> partition p1 from (1, unbounded) to (1, 1);
>> partition p2 from (1, 1) to (1, 10);
>> partition p3 from (1, 10) to (1, unbounded);
>> partition p4 from (2, unbounded) to (2, 1);
>> ... so on
>>
>> In this case, we need to be able to conclude, say, (1, -inf) < (1, 15) <
>> (1, +inf), so that tuple (1, 15) is assigned to the proper partition.
>>
>> Does this last thing sound reasonable?
>>
>> Thanks,
>> Amit
>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 06:04 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-24 06:04 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
Hi Ashutosh,
On 2016/11/24 14:35, Ashutosh Bapat wrote:
> I am trying to create a partitioned table with primary keys on the
> partitions. Here's the corresponding syntax as per documentation
> CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
> IF NOT EXISTS ] table_name
> PARTITION OF parent_table [ (
> { column_name WITH OPTIONS [ column_constraint [ ... ] ]
> | table_constraint }
> [, ... ]
> ) ] partition_bound_spec
>
> IIUC, it should allow "create table t1_p1 partition of t1 (a primary
> key) ...", (a primary key) is nothing but "column_name
> column_constraint", but here's what happens
> create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
> ERROR: syntax error at or near "primary"
> LINE 1: create table t1_p1 partition of t1 (a primary key) for value...
You have to specify column constraints using the keywords WITH OPTIONS,
like below:
create table p1 partition of p (
a with options primary key
) for values in (1);
> The same syntax also suggests using table_constraints but that too doesn't work
> create table t1_p1 partition of t1 (primary key (a) ) for values
> from (0) to (100);
> ERROR: inherited relation "t1" is not a table or foreign table
>
> of course t1 is a table, what it isn't?
It's a bug. Forgot to consider RELKIND_PARTITIONED_TABLE to an if block
in the code that checks inheritance parent relation's relkind when
creating an index constraint (primary key) on a child table. Will fix,
thanks for catching it.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 06:10 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-24 06:10 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> Hi Ashutosh,
>
> On 2016/11/24 14:35, Ashutosh Bapat wrote:
>> I am trying to create a partitioned table with primary keys on the
>> partitions. Here's the corresponding syntax as per documentation
>> CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [
>> IF NOT EXISTS ] table_name
>> PARTITION OF parent_table [ (
>> { column_name WITH OPTIONS [ column_constraint [ ... ] ]
>> | table_constraint }
>> [, ... ]
>> ) ] partition_bound_spec
>>
>> IIUC, it should allow "create table t1_p1 partition of t1 (a primary
>> key) ...", (a primary key) is nothing but "column_name
>> column_constraint", but here's what happens
>> create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
>> ERROR: syntax error at or near "primary"
>> LINE 1: create table t1_p1 partition of t1 (a primary key) for value...
>
> You have to specify column constraints using the keywords WITH OPTIONS,
> like below:
>
> create table p1 partition of p (
> a with options primary key
> ) for values in (1);
Oh, sorry for not noticing it. You are right. Why do we need "with
option" there? Shouldn't user be able to specify just "a primary key";
it's not really an "option", it's a constraint.
>
>> The same syntax also suggests using table_constraints but that too doesn't work
>> create table t1_p1 partition of t1 (primary key (a) ) for values
>> from (0) to (100);
>> ERROR: inherited relation "t1" is not a table or foreign table
>>
>> of course t1 is a table, what it isn't?
>
> It's a bug. Forgot to consider RELKIND_PARTITIONED_TABLE to an if block
> in the code that checks inheritance parent relation's relkind when
> creating an index constraint (primary key) on a child table. Will fix,
> thanks for catching it.
>
> Thanks,
> Amit
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 06:32 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-24 06:32 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
On 2016/11/24 15:10, Ashutosh Bapat wrote:
> On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:
>> On 2016/11/24 14:35, Ashutosh Bapat wrote:
>>> IIUC, it should allow "create table t1_p1 partition of t1 (a primary
>>> key) ...", (a primary key) is nothing but "column_name
>>> column_constraint", but here's what happens
>>> create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
>>> ERROR: syntax error at or near "primary"
>>> LINE 1: create table t1_p1 partition of t1 (a primary key) for value...
>>
>> You have to specify column constraints using the keywords WITH OPTIONS,
>> like below:
>>
>> create table p1 partition of p (
>> a with options primary key
>> ) for values in (1);
>
> Oh, sorry for not noticing it. You are right. Why do we need "with
> option" there? Shouldn't user be able to specify just "a primary key";
> it's not really an "option", it's a constraint.
I just adopted the existing syntax for specifying column/table constraints
of a table created with CREATE TABLE OF type_name.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 06:40 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-24 06:40 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
On Thu, Nov 24, 2016 at 12:02 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/11/24 15:10, Ashutosh Bapat wrote:
>> On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:
>>> On 2016/11/24 14:35, Ashutosh Bapat wrote:
>>>> IIUC, it should allow "create table t1_p1 partition of t1 (a primary
>>>> key) ...", (a primary key) is nothing but "column_name
>>>> column_constraint", but here's what happens
>>>> create table t1_p1 partition of t1 (a primary key) for values from (0) to (100);
>>>> ERROR: syntax error at or near "primary"
>>>> LINE 1: create table t1_p1 partition of t1 (a primary key) for value...
>>>
>>> You have to specify column constraints using the keywords WITH OPTIONS,
>>> like below:
>>>
>>> create table p1 partition of p (
>>> a with options primary key
>>> ) for values in (1);
>>
>> Oh, sorry for not noticing it. You are right. Why do we need "with
>> option" there? Shouldn't user be able to specify just "a primary key";
>> it's not really an "option", it's a constraint.
>
> I just adopted the existing syntax for specifying column/table constraints
> of a table created with CREATE TABLE OF type_name.
Hmm, I don't fine it quite intuitive. But others might find it so.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 11:13 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-24 11:13 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/23 4:50, Robert Haas wrote:
> On Tue, Nov 22, 2016 at 4:15 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> The easiest thing to do might be to just enforce that all of the
>>> partition key columns have to be not-null when the range-partitioned
>>> table is defined, and reject any attempt to DROP NOT NULL on them
>>> later. That's probably better that shoehorning it into the table
>>> constraint.
>>
>> Agreed that forcing range partitioning columns to be NOT NULL during table
>> creation would be a better approach. But then we would have to reject
>> using expressions in the range partition key, right?
>
> Why?
I was thinking of it like how primary key columns cannot contain
expressions; the reason for which, I assume, is because while we can
ensure that a column contains only non-null values by defining a
constraint on the column, there is no way to force expressions to be non-null.
In any case, I have implemented in the latest patch that when creating a
range partitioned table, its partition key columns are automatically set
to be NOT NULL. Although, it leaves out the columns that are referenced
in expressions. So even after doing so, we need to check after computing
the range partition key of a input row, that none of the partitions keys
is null, because expressions can still return null.
Also, it does nothing to help the undesirable situation that one can
insert a row with a null partition key (expression) into any of the range
partitions if targeted directly, because of how ExecQual() handles
nullable constraint expressions (treats null value as satisfying the
partition constraint).
An alternative possibly worth considering might be to somehow handle the
null range partition keys within the logic to compare against range bound
datums. It looks like other databases will map the rows containing nulls
to the unbounded partition. One database allows specifying NULLS
FIRST/LAST and maps a row containing null key to the partition with
-infinity as the lower bound or +infinity as the upper bound, respectively
with NULLS LAST the default behavior.
In our case, if we allowed a similar way of defining a range partitioned
table:
create table p (a int, b int) partition by range nulls first (a);
create table p0 partition of p for values from (unbounded) to (1);
create table p1 partition of p for values from (1) to (10);
create table p2 partition of p for values from (10) to (unbounded);
Row (null, 1) will be mapped to p0. If we didn't have p0, we would report
the "partition not found" error.
In case of a multi-column key:
create table p (a int, b int) partition by range (a, b);
create table p0 partition of p for values from (1, unbounded) to (1, 1);
create table p1 partition of p for values from (1, 1) to (1, 10);
create table p2 partition of p for values from (1, 10) to (1, unbounded);
Row (1, null) will be mapped to p2 (default nulls last behavior).
But I guess we still end up without a solution for the problem that a row
with null partition key (expression) could be inserted into any of the
range partitions if targeted directly.
Thoughts?
>> As a result, one change became necessary: to how we flag individual range
>> bound datum as infinite or not. Previously, it was a regular Boolean
>> value (either infinite or not) and to distinguish +infinity from
>> -infinity, we looked at whether the bound is lower or upper (the lower
>> flag). Now, instead, the variable holding the status of individual range
>> bound datum is set to a ternary value: RANGE_DATUM_FINITE (0),
>> RANGE_DATUM_NEG_INF (1), and RANGE_DATUM_POS_INF (2), which still fits in
>> a bool.
>
> You better not be using a bool to represent a ternary value! Use an
> enum for that -- or if in the system catalogs, a char.
OK, created an enum called RangeDatumContent. In the system catalog, we
still store the boolean value; it is only after we read it into the
relcache structure that we use one of these enum values. I'm worried
though that using enum would consume more memory (we need to store nparts
* partnattrs instances of the enum).
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-24 19:36 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Alvaro Herrera @ 2016-11-24 19:36 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
Amit Langote wrote:
> On 2016/11/24 15:10, Ashutosh Bapat wrote:
> > On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:
> >> You have to specify column constraints using the keywords WITH OPTIONS,
> >> like below:
> >>
> >> create table p1 partition of p (
> >> a with options primary key
> >> ) for values in (1);
> >
> > Oh, sorry for not noticing it. You are right. Why do we need "with
> > option" there? Shouldn't user be able to specify just "a primary key";
> > it's not really an "option", it's a constraint.
>
> I just adopted the existing syntax for specifying column/table constraints
> of a table created with CREATE TABLE OF type_name.
I think CREATE TABLE OF is pretty much a corner case. I agree that
allowing the constraint right after the constraint name is more
intuitive.
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-25 01:08 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
0 siblings, 2 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-25 01:08 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
On 2016/11/25 4:36, Alvaro Herrera wrote:
> Amit Langote wrote:
>> On 2016/11/24 15:10, Ashutosh Bapat wrote:
>>> On Thu, Nov 24, 2016 at 11:34 AM, Amit Langote wrote:
>
>>>> You have to specify column constraints using the keywords WITH OPTIONS,
>>>> like below:
>>>>
>>>> create table p1 partition of p (
>>>> a with options primary key
>>>> ) for values in (1);
>>>
>>> Oh, sorry for not noticing it. You are right. Why do we need "with
>>> option" there? Shouldn't user be able to specify just "a primary key";
>>> it's not really an "option", it's a constraint.
>>
>> I just adopted the existing syntax for specifying column/table constraints
>> of a table created with CREATE TABLE OF type_name.
>
> I think CREATE TABLE OF is pretty much a corner case. I agree that
> allowing the constraint right after the constraint name is more
> intuitive.
I assume you meant "...right after the column name"?
I will modify the grammar to allow that way then, so that the following
will work:
create table p1 partition of p (
a primary key
) for values in (1);
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-25 02:44 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-25 02:44 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/11/23 4:50, Robert Haas wrote:
>> On Tue, Nov 22, 2016 at 4:15 AM, Amit Langote
>> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>>> The easiest thing to do might be to just enforce that all of the
>>>> partition key columns have to be not-null when the range-partitioned
>>>> table is defined, and reject any attempt to DROP NOT NULL on them
>>>> later. That's probably better that shoehorning it into the table
>>>> constraint.
>>>
>>> Agreed that forcing range partitioning columns to be NOT NULL during table
>>> creation would be a better approach. But then we would have to reject
>>> using expressions in the range partition key, right?
>>
>> Why?
>
> I was thinking of it like how primary key columns cannot contain
> expressions; the reason for which, I assume, is because while we can
> ensure that a column contains only non-null values by defining a
> constraint on the column, there is no way to force expressions to be non-null.
>
> In any case, I have implemented in the latest patch that when creating a
> range partitioned table, its partition key columns are automatically set
> to be NOT NULL. Although, it leaves out the columns that are referenced
> in expressions. So even after doing so, we need to check after computing
> the range partition key of a input row, that none of the partitions keys
> is null, because expressions can still return null.
Right. And ensuring that those columns were NOT NULL would be wrong,
as it wouldn't guarantee a non-null result anyway.
> Also, it does nothing to help the undesirable situation that one can
> insert a row with a null partition key (expression) into any of the range
> partitions if targeted directly, because of how ExecQual() handles
> nullable constraint expressions (treats null value as satisfying the
> partition constraint).
That's going to have to be fixed somehow. How bad would it be if we
passed ExecQual's third argument as false for partition constraints?
Or else you could generate the actual constraint as expr IS NOT NULL
AND expr >= lb AND expr < ub.
> An alternative possibly worth considering might be to somehow handle the
> null range partition keys within the logic to compare against range bound
> datums. It looks like other databases will map the rows containing nulls
> to the unbounded partition. One database allows specifying NULLS
> FIRST/LAST and maps a row containing null key to the partition with
> -infinity as the lower bound or +infinity as the upper bound, respectively
> with NULLS LAST the default behavior.
It seems more future-proof not to allow NULLs at all for now, and
figure out what if anything we want to do about that later. I mean,
with the syntax we've got here, anything else is basically deciding
whether NULL is the lowest value or the highest value. It would be
convenient for my employer if we made the same decision that Oracle
did, here, but it doesn't really seem like the PostgreSQL way - or to
put that another way, it's really ugly and unprincipled. So I
recommend we decide for now that a partitioning column can't be null
and a partitioning expression can't evaluate to NULL. If it does,
ERROR.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-25 03:11 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Alvaro Herrera @ 2016-11-25 03:11 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
Amit Langote wrote:
> On 2016/11/25 4:36, Alvaro Herrera wrote:
> > I think CREATE TABLE OF is pretty much a corner case. I agree that
> > allowing the constraint right after the constraint name is more
> > intuitive.
>
> I assume you meant "...right after the column name"?
Eh, right.
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-25 04:51 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-25 04:51 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
>
> I assume you meant "...right after the column name"?
>
> I will modify the grammar to allow that way then, so that the following
> will work:
>
> create table p1 partition of p (
> a primary key
> ) for values in (1);
>
That seems to be non-intuitive as well. The way it's written it looks
like "a primary key" is associated with p rather than p1.
Is there any column constraint that can not be a table constraint? If
no, then we can think of dropping column constraint syntax all
together and let the user specify column constraints through table
constraint syntax. OR we may drop constraints all-together from the
"CREATE TABLE .. PARTITION OF" syntax and let user handle it through
ALTER TABLE commands. In a later version, we will introduce constraint
syntax in that DDL.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-25 09:16 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 329+ messages in thread
From: Amit Langote @ 2016-11-25 09:16 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
On 2016/11/25 13:51, Ashutosh Bapat wrote:
>>
>> I assume you meant "...right after the column name"?
>>
>> I will modify the grammar to allow that way then, so that the following
>> will work:
>>
>> create table p1 partition of p (
>> a primary key
>> ) for values in (1);
>>
>
> That seems to be non-intuitive as well. The way it's written it looks
> like "a primary key" is associated with p rather than p1.
It kind of does, but it's still a "create table p1" statement, so it's not
that ambiguous, IMHO. Although I'm not attached to this syntax, there may
not be other options, such as moving the parenthesized list right before
"partition of", due to resulting grammar conflicts.
> Is there any column constraint that can not be a table constraint?
NOT NULL, DEFAULT, and COLLATE (although only the first of these is really
a constraint, albeit without a pg_constraint entry)
> If no, then we can think of dropping column constraint syntax all
> together and let the user specify column constraints through table
> constraint syntax. OR we may drop constraints all-together from the
> "CREATE TABLE .. PARTITION OF" syntax and let user handle it through
> ALTER TABLE commands. In a later version, we will introduce constraint
> syntax in that DDL.
Hmm, it was like that in the past versions of the patch, but I thought
it'd be better to follow the CREATE TABLE OF style to allow specifying the
table and column constraints during table creation time. If many think
that it is not required (or should have some other syntax), I will modify
the patch accordingly.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-25 10:49 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-25 10:49 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/25 11:44, Robert Haas wrote:
> On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote wrote:
>> Also, it does nothing to help the undesirable situation that one can
>> insert a row with a null partition key (expression) into any of the range
>> partitions if targeted directly, because of how ExecQual() handles
>> nullable constraint expressions (treats null value as satisfying the
>> partition constraint).
>
> That's going to have to be fixed somehow. How bad would it be if we
> passed ExecQual's third argument as false for partition constraints?
> Or else you could generate the actual constraint as expr IS NOT NULL
> AND expr >= lb AND expr < ub.
About the former, I think that might work. If a column is NULL, it would
be caught in ExecConstraints() even before ExecQual() is called, because
of the NOT NULL constraint. If an expression is NULL, or for some reason,
the partitioning operator (=, >=, or <) returned NULL even for a non-NULL
column or expression, then ExecQual() would fail if we passed false for
resultForNull. Not sure if that would be violating the SQL specification
though.
The latter would work too. But I guess we would only emit expr IS NOT
NULL, not column IS NOT NULL, because columns are covered by NOT NULL
constraints.
>> An alternative possibly worth considering might be to somehow handle the
>> null range partition keys within the logic to compare against range bound
>> datums. It looks like other databases will map the rows containing nulls
>> to the unbounded partition. One database allows specifying NULLS
>> FIRST/LAST and maps a row containing null key to the partition with
>> -infinity as the lower bound or +infinity as the upper bound, respectively
>> with NULLS LAST the default behavior.
>
> It seems more future-proof not to allow NULLs at all for now, and
> figure out what if anything we want to do about that later. I mean,
> with the syntax we've got here, anything else is basically deciding
> whether NULL is the lowest value or the highest value. It would be
> convenient for my employer if we made the same decision that Oracle
> did, here, but it doesn't really seem like the PostgreSQL way - or to
> put that another way, it's really ugly and unprincipled. So I
> recommend we decide for now that a partitioning column can't be null
> and a partitioning expression can't evaluate to NULL. If it does,
> ERROR.
OK, we can decide later if we want to handle NULLs somehow.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-28 14:42 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Ashutosh Bapat @ 2016-11-28 14:42 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Rushabh Lathia <rushabh.lathia@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; pgsql-hackers
Here are some comments on 0003 patch.
1. In ALTER TABLE documentation we should refer to CREATE TABLE documentation
for partition_bound_spec syntax, similar ADD table_constraint note.
2. I think, "of the target table" is missing after all the ... constraints
+ match. Also, it must have all the <literal>NOT NULL</literal> and
+ <literal>CHECK</literal> constraints present in the target table.
3. I think, using "any of the" instead of "some" is preferred in the following
sentence. In the following sentence, we should use "such a constraint" instead
of "constraints" to avoid mixed singular and plural usage.
+ If some <literal>CHECK</literal> constraint of the table being attached
4. This construction is ambiguous. "are not considered" - considered where? for
what? Constraints on which object?
+ clause. Currently <literal>UNIQUE</literal>, <literal>PRIMARY
KEY</literal>,
+ and <literal>FOREIGN KEY</literal> constraints are not considered.
5. What is a partition constraint? Do we define that term anywhere in the
documentation? If not we should define that term and then use it here.
+ A full table scan is performed on the table being attached to check that
+ no existing row in the table violates the partition constraint. It is
6. The paragraph following
+ A full table scan is performed on the table
being attached to check that seems to be confusing. It says that the
potentially expensive scan can be avoided by adding a constraint equivalent to
partition constraint. That seems to be wrong. The table will be scanned anyway,
when adding a valid constraint per ALTER TABLE ADD table_constraint
documentation. Instead you might want to say that the table scan will not be
carried out if the existing constraints imply the partition constraints.
7. You might want to specify the fate of range or lists covered by the
partition being detached in DETACH PARTITION section.
8. Since partition bound specification is more than a few words, you might want
to refer to the actual syntax in CREATE TABLE command.
+ <term><replaceable
class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
9. I think, we need to use "may be scanned" instead of "is scanned", given that
some other part of the documentation talks about "avoiding" the table scan. May
be refer to that documentation to clarify the uncertainty.
+ Similarly, when attaching a new partition it is scanned to verify that
10. The same paragraph talks about multiple ALTER TABLE commands being run
together to avoid multiple table rewrites. Do we want to document whether
ATTACH/DETACH partition can be run with any other command or not.
11. ATTACH partition documentation is not talking about the unlogged or
temporary tables being attached to is logged or non-temporary. What is current
code doing about these cases? Do we allow such mixed partition hierarchy?
+ existing rows meet the partition constraint.
I will continue to look at the patch.
On Thu, Nov 24, 2016 at 4:04 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> Hi Rushabh,
>
> On 2016/11/22 22:11, Rushabh Lathia wrote:
>> Hi Amit,
>>
>> I was just reading through your patches and here are some quick review
>> comments
>> for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch.
>
> Thanks for the review!
>
>>
>> Review comments for 0001-Catalog-and-DDL-for-partitioned-tables-17.patch:
>>
>> 1)
>> @@ -1102,9 +1104,10 @@ heap_create_with_catalog(const char *relname,
>> {
>> /* Use binary-upgrade override for pg_class.oid/relfilenode? */
>> if (IsBinaryUpgrade &&
>> - (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
>> - relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
>> - relkind == RELKIND_COMPOSITE_TYPE || relkind ==
>> RELKIND_FOREIGN_TABLE))
>> + (relkind == RELKIND_RELATION || relkind ==
>> RELKIND_PARTITIONED_TABLE ||
>> + relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
>> + relkind == RELKIND_MATVIEW || relkind ==
>> RELKIND_COMPOSITE_TYPE ||
>> + relkind == RELKIND_FOREIGN_TABLE))
>>
>> You should add the RELKIND_PARTITIONED_TABLE at the end of if condition that
>> will make diff minimal. While reading through the patch I noticed that there
>> is inconsistency - someplace its been added at the end and at few places its
>> at the start. I think you can make add it at the end of condition and be
>> consistent with each place.
>
> OK, done.
>
>>
>> 2)
>>
>> + /*
>> + * We need to transform the raw parsetrees corresponding to
>> partition
>> + * expressions into executable expression trees. Like column
>> defaults
>> + * and CHECK constraints, we could not have done the transformation
>> + * earlier.
>> + */
>>
>>
>> Additional space before "Like column defaults".
>
> I think it's a common practice to add two spaces after a sentence-ending
> period [https://www.gnu.org/prep/standards/html_node/Comments.html], which
> it seems, is followed more or less regularly in the formatting of the
> comments in the PostgreSQL source code.
>
>> 3)
>> - char relkind;
>> + char relkind,
>> + expected_relkind;
>>
>> Newly added variable should be define separately with its type. Something
>> like:
>>
>> char relkind;
>> + char expected_relkind;
>
> OK, done.
>
>>
>> 4)
>>
>> a)
>> + /* Prevent partitioned tables from becoming inheritance parents */
>> + if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>> + errmsg("cannot inherit from partitioned table \"%s\"",
>> + parent->relname)));
>> +
>>
>> need alignment for last line.
>
> Fixed.
>
>> b)
>> + atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
>> pelem->name);
>> + if (!HeapTupleIsValid(atttuple))
>> + ereport(ERROR,
>> + (errcode(ERRCODE_UNDEFINED_COLUMN),
>> + errmsg("column \"%s\" named in partition key does
>> not exist",
>> + pelem->name)));
>> + attform = (Form_pg_attribute) GETSTRUCT(atttuple);
>> +
>> + if (attform->attnum <= 0)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_UNDEFINED_COLUMN),
>> + errmsg("cannot use system column \"%s\" in
>> partition key",
>> + pelem->name)));
>>
>> need alignment for last line of ereport
>
> Fixed.
>
>> c)
>> + /* Disallow ROW triggers on partitioned tables */
>> + if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
>> + ereport(ERROR,
>> + (errcode(ERRCODE_WRONG_OBJECT_TYPE),
>> + errmsg("\"%s\" is a partitioned table",
>> + RelationGetRelationName(rel)),
>> + errdetail("Partitioned tables cannot have ROW triggers.")));
>>
>> need alignment
>
> Fixed.
>
>> 5)
>>
>> @@ -2512,6 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt
>> *stmt,
>> cxt.blist = NIL;
>> cxt.alist = NIL;
>> cxt.pkey = NULL;
>> + cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
>>
>> I think adding bracket will look code more clear.
>>
>> + cxt.ispartitioned = (rel->rd_rel->relkind ==
>> RELKIND_PARTITIONED_TABLE);
>
> Agreed, done.
>
>> 6)
>>
>> + * RelationBuildPartitionKey
>> + * Build and attach to relcache partition key data of relation
>> + *
>> + * Partitioning key data is stored in CacheMemoryContext to ensure it
>> survives
>> + * as long as the relcache. To avoid leaking memory in that context in
>> case
>> + * of an error partway through this function, we build the structure in the
>> + * working context (which must be short-lived) and copy the completed
>> + * structure into the cache memory.
>>
>> extra space before "To avoid leaking memory"
>
> Same thing I said above.
>
>> 7)
>> + /* variable-length fields start here, but we allow direct access to
>> partattrs */
>> + int2vector partattrs; /* attribute numbers of columns in
>> the
>>
>> Why partattrs is allow direct access - its not really clear from the
>> comments.
>
> I have copied the comment from another catalog header file (a number of
> catalog headers have the same comment). I updated the new file's comment
> to say a little bit more; I wonder if the comment should be updated in
> other files as well? However, I noticed that there are explanatory notes
> elsewhere (for example, around the code that reads such a field from the
> catalog) about why the first variable-length field of a catalog's tuple
> (especially of type int2vector or oidvector) are directly accessible via
> their C struct offsets.
>
>> I will continue reading more patch and testing functionality.. will share
>> the
>> comments as I have it.
>
> Updated patches attached. I have also considered Robert's comments [1] as
> follows and fixed a bug that Ashutosh reported [2]:
>
> - Force partition key columns to be NOT NULL at the table creation time
> if using range partitioning
> - Use enum to represent the content of individual range datums (viz.
> finite datum, -infinity, +infinity) in the relcache struct
>
> Thanks,
> Amit
>
> [1]
> https://www.postgresql.org/message-id/CA+TgmoZ-feQsxc7U_JerM_AFChp3Qf6btK708SAe7M8Vdv5=jA@mail.gmail...
> [2]
> https://www.postgresql.org/message-id/306c85e9-c702-3742-eeff-9b7a40498afc%40lab.ntt.co.jp
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-29 11:24 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-29 11:24 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On 2016/11/17 20:27, Amit Langote wrote:
> On 2016/11/16 4:21, Robert Haas wrote:
>> Have you done any performance testing on the tuple routing code?
>> Suppose we insert a million (or 10 million) tuples into an
>> unpartitioned table, a table with 10 partitions, a table with 100
>> partitions, a table with 1000 partitions, and a table that is
>> partitioned into 10 partitions each of which has 10 subpartitions.
>> Ideally, the partitioned cases would run almost as fast as the
>> unpartitioned case, but probably there will be some overhead.
>> However, it would be useful to know how much. Also, it would be
>> useful to set up the same cases with inheritance using a PL/pgsql ON
>> INSERT trigger for tuple routing and compare. Hopefully the tuple
>> routing code is far faster than a trigger, but we should make sure
>> that's the case and look for optimizations if not. Also, it would be
>> useful to know how much slower the tuple-mapping-required case is than
>> the no-tuple-mapping-required case.
>
> OK, I will share the performance results soon.
Sorry about the delay; here are some numbers with the following
partitioning schema:
# plain table
create table plain (a date, b int, c int);
# partitioned table
create table ptab (a date, b int, c int) partition by range (a, b);
Partitions (not the full commands):
ptab_00001 for values from ('2016-11-29', 1) to ('2016-11-29', 1000);
ptab_00002 for values from ('2016-11-29', 1000) to ('2016-11-29', 2000);
...
ptab_00005 for values from ('2016-11-29', 4000) to ('2016-11-29', 5000);
ptab_00006 for values from ('2016-11-30', 1) to ('2016-11-30', 1000);
...
...
ptab_NNNNN for values from ('20XX-XX-XX', 4000) to ('20XX-XX-XX', 5000);
# inheritance partitioned table
create table itab (a date, b int, c int);
create table itab_00001 (
check part_check check (a = '2016-11-29' and b >= 1 and b < 1000)
) inherits (itab);
...
create table itab_00005 (
check part_check check (a = '2016-11-29' and b >= 4000 and b < 5000)
) inherits (itab);
create table itab_0006 (
check part_check check (a = '2016-11-30' and b >= 1and b < 1000)
) inherits (itab);
...
...
create table itab_NNNNN (
check part_check check (a = '2016-11-29' and b >= 4000 and b < 5000)
) inherits (itab);
The BR trigger (on itab) procedure as follows:
CREATE OR REPLACE FUNCTION itab_ins_trig()
RETURNS TRIGGER AS $$
DECLARE
partno text;
BEGIN
SELECT to_char((NEW.a - '2016-11-29'::date) * 5 + NEW.b / 1000 + 1,
'fm00000') INTO partno;
EXECUTE 'INSERT INTO itab_' || partno || ' SELECT $1.*' USING NEW;
RETURN NULL;
END; $$ LANGUAGE plpgsql;
Note that the tuple-routing procedure above assumes a fixed-stride range
partitioning scheme (shown as tg-direct-map below). In other cases, the
simplest approach involves defining a if-else ladder, which I tried too
(shown as tg-if-else below), but reporting times only for up to 200
partitions at most (I'm sure there might be ways to be smarter there
somehow, but I didn't; the point here may only be to compare the new
tuple-routing code's overhead vs. trigger overhead in the traditional method).
# All times in seconds (on my modestly-powerful development VM)
#
# nrows = 10,000,000 generated using:
#
# INSERT INTO $tab
# SELECT '$last'::date - ((s.id % $maxsecs + 1)::bigint || 's')::interval,
# (random() * 5000)::int % 4999 + 1,
# case s.id % 10
# when 0 then 'a'
# when 1 then 'b'
# when 2 then 'c'
# ...
# when 9 then 'j'
# end
# FROM generate_series(1, $nrows) s(id)
# ORDER BY random();
#
# The first item in the select list is basically a date that won't fall
# outside the defined partitions.
Time for a plain table = 98.1 sec
#part parted tg-direct-map tg-if-else
===== ====== ============= ==========
10 114.3 1483.3 742.4
50 112.5 1476.6 2016.8
100 117.1 1498.4 5386.1
500 125.3 1475.5 --
1000 129.9 1474.4 --
5000 137.5 1491.4 --
10000 154.7 1480.9 --
Then for a 2-level partitioned table with each of the above partitions
partitioned by list (c), with 10 sub-partitions each as follows:
ptab_NNNNN_a for values in ('a');
ptab_NNNNN_b for values in ('b');
...
ptab_NNNNN_k for values in ('j');
I didn't include the times for inheritance table with a routing trigger in
this case, as it seems that the results would look something like the above:
Time for a plain table = 98.1 sec
#part (sub-)parted
===== ============
10 127.0
50 152.3
100 156.6
500 191.8
1000 187.3
Regarding tuple-mapping-required vs no-tuple-mapping-required, all cases
currently require tuple-mapping, because the decision is based on the
result of comparing parent and partition TupleDesc using
equalTupleDescs(), which fails so quickly because TupleDesc.tdtypeid are
not the same. Anyway, I simply commented out the tuple-mapping statement
in ExecInsert() to observe just slightly improved numbers as follows
(comparing with numbers in the table just above):
#part (sub-)parted
===== =================
10 113.9 (vs. 127.0)
100 135.7 (vs. 156.6)
500 182.1 (vs. 191.8)
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-30 15:48 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-11-30 15:48 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Fri, Nov 25, 2016 at 5:49 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/11/25 11:44, Robert Haas wrote:
>> On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote wrote:
>>> Also, it does nothing to help the undesirable situation that one can
>>> insert a row with a null partition key (expression) into any of the range
>>> partitions if targeted directly, because of how ExecQual() handles
>>> nullable constraint expressions (treats null value as satisfying the
>>> partition constraint).
>>
>> That's going to have to be fixed somehow. How bad would it be if we
>> passed ExecQual's third argument as false for partition constraints?
>> Or else you could generate the actual constraint as expr IS NOT NULL
>> AND expr >= lb AND expr < ub.
>
> About the former, I think that might work. If a column is NULL, it would
> be caught in ExecConstraints() even before ExecQual() is called, because
> of the NOT NULL constraint. If an expression is NULL, or for some reason,
> the partitioning operator (=, >=, or <) returned NULL even for a non-NULL
> column or expression, then ExecQual() would fail if we passed false for
> resultForNull. Not sure if that would be violating the SQL specification
> though.
I don't think the SQL specification can have anything to say about an
implicit constraint generated as an implementation detail of our
partitioning implementation.
> The latter would work too. But I guess we would only emit expr IS NOT
> NULL, not column IS NOT NULL, because columns are covered by NOT NULL
> constraints.
Right.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-30 15:54 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-11-30 15:54 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Tue, Nov 29, 2016 at 6:24 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> # All times in seconds (on my modestly-powerful development VM)
> #
> # nrows = 10,000,000 generated using:
> #
> # INSERT INTO $tab
> # SELECT '$last'::date - ((s.id % $maxsecs + 1)::bigint || 's')::interval,
> # (random() * 5000)::int % 4999 + 1,
> # case s.id % 10
> # when 0 then 'a'
> # when 1 then 'b'
> # when 2 then 'c'
> # ...
> # when 9 then 'j'
> # end
> # FROM generate_series(1, $nrows) s(id)
> # ORDER BY random();
> #
> # The first item in the select list is basically a date that won't fall
> # outside the defined partitions.
>
> Time for a plain table = 98.1 sec
>
> #part parted tg-direct-map tg-if-else
> ===== ====== ============= ==========
> 10 114.3 1483.3 742.4
> 50 112.5 1476.6 2016.8
> 100 117.1 1498.4 5386.1
> 500 125.3 1475.5 --
> 1000 129.9 1474.4 --
> 5000 137.5 1491.4 --
> 10000 154.7 1480.9 --
Very nice!
Obviously, it would be nice if the overhead were even lower, but it's
clearly a vast improvement over what we have today.
> Regarding tuple-mapping-required vs no-tuple-mapping-required, all cases
> currently require tuple-mapping, because the decision is based on the
> result of comparing parent and partition TupleDesc using
> equalTupleDescs(), which fails so quickly because TupleDesc.tdtypeid are
> not the same. Anyway, I simply commented out the tuple-mapping statement
> in ExecInsert() to observe just slightly improved numbers as follows
> (comparing with numbers in the table just above):
>
> #part (sub-)parted
> ===== =================
> 10 113.9 (vs. 127.0)
> 100 135.7 (vs. 156.6)
> 500 182.1 (vs. 191.8)
I think you should definitely try to get that additional speedup when
you can. It doesn't seem like a lot when you think of how much is
already being saved, but a healthy number of users are going to
compare it to the performance on an unpartitioned table rather than to
our historical performance. 127/98.1 = 1.29, but 113.9/98.1 = 1.16
-- and obviously a 16% overhead from partitioning is way better than a
29% overhead, even if the old overhead was a million percent.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-11-30 15:56 Amit Langote <amitlangote09@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-11-30 15:56 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Thu, Dec 1, 2016 at 12:48 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Fri, Nov 25, 2016 at 5:49 AM, Amit Langote wrote:
>> On 2016/11/25 11:44, Robert Haas wrote:
>>> On Thu, Nov 24, 2016 at 6:13 AM, Amit Langote wrote:
>>>> Also, it does nothing to help the undesirable situation that one can
>>>> insert a row with a null partition key (expression) into any of the range
>>>> partitions if targeted directly, because of how ExecQual() handles
>>>> nullable constraint expressions (treats null value as satisfying the
>>>> partition constraint).
>>>
>>> That's going to have to be fixed somehow. How bad would it be if we
>>> passed ExecQual's third argument as false for partition constraints?
>>> Or else you could generate the actual constraint as expr IS NOT NULL
>>> AND expr >= lb AND expr < ub.
>>
>> About the former, I think that might work. If a column is NULL, it would
>> be caught in ExecConstraints() even before ExecQual() is called, because
>> of the NOT NULL constraint. If an expression is NULL, or for some reason,
>> the partitioning operator (=, >=, or <) returned NULL even for a non-NULL
>> column or expression, then ExecQual() would fail if we passed false for
>> resultForNull. Not sure if that would be violating the SQL specification
>> though.
>
> I don't think the SQL specification can have anything to say about an
> implicit constraint generated as an implementation detail of our
> partitioning implementation.
Yeah, I thought so too.
>> The latter would work too. But I guess we would only emit expr IS NOT
>> NULL, not column IS NOT NULL, because columns are covered by NOT NULL
>> constraints.
>
> Right.
The latest patch I posted earlier today has this implementation.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-12-07 04:38 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-12-07 04:38 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Nov 30, 2016 at 10:56 AM, Amit Langote <amitlangote09@gmail.com> wrote:
> The latest patch I posted earlier today has this implementation.
I decided to try out these patches today with #define
CLOBBER_CACHE_ALWAYS 1 in pg_config_manual.h, which found a couple of
problems:
1. RelationClearRelation() wasn't preserving the rd_partkey, even
though there's plenty of code that relies on it not changing while we
hold a lock on the relation - in particular, transformPartitionBound.
2. partition_bounds_equal() was using the comparator and collation for
partitioning column 0 to compare the datums for all partitioning
columns. It's amazing this passed the regression tests.
The attached incremental patch fixes those things and some cosmetic
issues I found along the way.
3. RelationGetPartitionDispatchInfo() is badly broken:
1010 pd[i] = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
1011 pd[i]->relid = RelationGetRelid(partrel);
1012 pd[i]->key = RelationGetPartitionKey(partrel);
1013 pd[i]->keystate = NIL;
1014 pd[i]->partdesc = partdesc;
1015 pd[i]->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
1016 heap_close(partrel, NoLock);
1017
1018 m = 0;
1019 for (j = 0; j < partdesc->nparts; j++)
1020 {
1021 Oid partrelid = partdesc->oids[j];
This code imagines that pointers it extracted from partrel are certain
to remain valid after heap_close(partrel, NoLock), perhaps on the
strength of the fact that we still retain a lock on the relation. But
this isn't the case. As soon as nobody has the relation open, a call
to RelationClearRelation() will destroy the relcache entry and
everything to which it points; with CLOBBER_CACHE_ALWAYS, I see a
failure at line 1021:
#0 RelationGetPartitionDispatchInfo (rel=0x1136dddf8, lockmode=3,
leaf_part_oids=0x7fff5633b938) at partition.c:1021
1021 Oid partrelid = partdesc->oids[j];
(gdb) bt 5
#0 RelationGetPartitionDispatchInfo (rel=0x1136dddf8, lockmode=3,
leaf_part_oids=0x7fff5633b938) at partition.c:1021
#1 0x0000000109b8d71f in ExecInitModifyTable (node=0x7fd12984d750,
estate=0x7fd12b885438, eflags=0) at nodeModifyTable.c:1730
#2 0x0000000109b5e7ac in ExecInitNode (node=0x7fd12984d750,
estate=0x7fd12b885438, eflags=0) at execProcnode.c:159
#3 0x0000000109b58548 in InitPlan (queryDesc=0x7fd12b87b638,
eflags=0) at execMain.c:961
#4 0x0000000109b57dcd in standard_ExecutorStart
(queryDesc=0x7fd12b87b638, eflags=0) at execMain.c:239
(More stack frames follow...)
Current language: auto; currently minimal
(gdb) p debug_query_string
$1 = 0x7fd12b84c238 "insert into list_parted values (null, 1);"
(gdb) p partdesc[0]
$2 = {
nparts = 2139062143,
oids = 0x7f7f7f7f7f7f7f7f,
boundinfo = 0x7f7f7f7f7f7f7f7f
}
As you can see, the partdesc is no longer valid here. I'm not
immediately sure how to fix this; this isn't a simple thinko. You
need to keep the relations open for the whole duration of the query,
not just long enough to build the dispatch info. I think you should
try to revise this so that each relation is opened once and kept open;
maybe the first loop should be making a pointer-list of Relations
rather than an int-list of relation OIDs. And it seems to me (though
I'm getting a little fuzzy here because it's late) that you need all
of the partitions open, not just the ones that are subpartitioned,
because how else are you going to know how to remap the tuple if the
column order is different? But I don't see this code doing that,
which makes me wonder if the partitions are being opened yet again in
some other location.
I recommend that once you fix this, you run 'make check' with #define
CLOBBER_CACHE_ALWAYS 1 and look for other hazards. Such mistakes are
easy to make with this kind of patch.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] partition-fixes-rmh.patch (5.8K, ../../CA+Tgmob9o3+xgYHtGbfzUWm9bknRryx53Q5pEVFkzVHSDO=QXQ@mail.gmail.com/2-partition-fixes-rmh.patch)
download | inline diff:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 83dc151..cc9009d 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -53,16 +53,16 @@
* Information about bounds of a partitioned relation
*
* A list partition datum that is known to be NULL is never put into the
- * datums array, instead it is tracked using has_null and null_index fields.
+ * datums array. Instead, it is tracked using has_null and null_index fields.
*
- * In case of range partitioning, ndatums is far less than 2 * nparts, because
- * a partition's upper bound and the next partition's lower bound are same
- * in most common cases, and we only store one of them.
+ * In the case of range partitioning, ndatums will typically be far less than
+ * 2 * nparts, because a partition's upper bound and the next partition's lower
+ * bound are the same in most common cases, and we only store one of them.
*
- * In case of list partitioning, the indexes array stores one entry for every
- * datum, which is the index of the partition that accepts a given datum.
- * Wheareas, in case of range partitioning, it stores one entry per distinct
- * range datum, which is the index of the partition of which a given datum
+ * In the case of list partitioning, the indexes array stores one entry for
+ * every datum, which is the index of the partition that accepts a given datum.
+ * In case of range partitioning, it stores one entry per distinct range
+ * datum, which is the index of the partition for which a given datum
* is an upper bound.
*/
@@ -135,16 +135,19 @@ typedef struct PartitionDispatchData
int *indexes;
} PartitionDispatchData;
-static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg);
-static int32 qsort_partition_rbound_cmp(const void *a, const void *b, void *arg);
+static int32 qsort_partition_list_value_cmp(const void *a, const void *b,
+ void *arg);
+static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
+ void *arg);
static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
-static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy,
- bool *need_relabel);
+static Oid get_partition_operator(PartitionKey key, int col,
+ StrategyNumber strategy, bool *need_relabel);
static List *generate_partition_qual(Relation rel, bool recurse);
-static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, List *datums, bool lower);
+static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
+ List *datums, bool lower);
static int32 partition_rbound_cmp(PartitionKey key,
Datum *datums1, RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2);
@@ -152,9 +155,11 @@ static int32 partition_rbound_datum_cmp(PartitionKey key,
Datum *rb_datums, RangeDatumContent *rb_content,
Datum *tuple_datums);
-static int32 partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
+static int32 partition_bound_cmp(PartitionKey key,
+ PartitionBoundInfo boundinfo,
int offset, void *probe, bool probe_is_bound);
-static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
+static int partition_bound_bsearch(PartitionKey key,
+ PartitionBoundInfo boundinfo,
void *probe, bool probe_is_bound, bool *is_equal);
/* Support get_partition_for_tuple() */
@@ -636,8 +641,8 @@ partition_bounds_equal(PartitionKey key,
{
int32 cmpval;
- cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
- key->partcollation[0],
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[j],
+ key->partcollation[j],
b1->datums[i][j],
b2->datums[i][j]));
if (cmpval != 0)
@@ -976,7 +981,7 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode,
PartitionDesc partdesc = RelationGetPartitionDesc(partrel);
/*
- * If this partition is a partitined table, add its children to to the
+ * If this partition is a partitioned table, add its children to the
* end of the list, so that they are processed as well.
*/
if (partdesc)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index abfb46b..c77b216 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1990,9 +1990,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
pfree(newattno);
/*
- * Close the parent rel, but keep our ShareUpdateExclusiveLock on it
- * until xact commit. That will prevent someone else from deleting or
- * ALTERing the parent before the child is committed.
+ * Close the parent rel, but keep our lock on it until xact commit.
+ * That will prevent someone else from deleting or ALTERing the parent
+ * before the child is committed.
*/
heap_close(relation, NoLock);
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c958092..2da7ae3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1192,7 +1192,7 @@ equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
/*
* Now compare partition bound collections. The logic to iterate over
- * the collections is local to partition.c.
+ * the collections is private to partition.c.
*/
if (partdesc1->boundinfo != NULL)
{
@@ -2604,7 +2604,9 @@ RelationClearRelation(Relation relation, bool rebuild)
SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
-
+ /* partition key must be preserved */
+ SWAPFIELD(PartitionKey, rd_partkey);
+ SWAPFIELD(MemoryContext, rd_partkeycxt);
/* preserve old partdesc if no logical change */
if (keep_partdesc)
{
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-12-07 14:45 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 329+ messages in thread
From: Robert Haas @ 2016-12-07 14:45 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers
On Wed, Dec 7, 2016 at 6:42 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/12/07 13:38, Robert Haas wrote:
>> On Wed, Nov 30, 2016 at 10:56 AM, Amit Langote <amitlangote09@gmail.com> wrote:
>>> The latest patch I posted earlier today has this implementation.
>>
>> I decided to try out these patches today with #define
>> CLOBBER_CACHE_ALWAYS 1 in pg_config_manual.h, which found a couple of
>> problems:
>>
>> 1. RelationClearRelation() wasn't preserving the rd_partkey, even
>> though there's plenty of code that relies on it not changing while we
>> hold a lock on the relation - in particular, transformPartitionBound.
>
> Oh, I thought an AccessExclusiveLock on the relation would prevent having
> to worry about that, but guess I'm wrong. Perhaps, having a lock on a
> table does not preclude RelationClearRelation() resetting the table's
> relcache.
No, it sure doesn't. The lock prevents the table from actually being
changed, so a reload will find data equivalent to what it had before,
but it doesn't prevent the backend's cache from being flushed.
>> 2. partition_bounds_equal() was using the comparator and collation for
>> partitioning column 0 to compare the datums for all partitioning
>> columns. It's amazing this passed the regression tests.
>
> Oops, it seems that the regression tests where the above code might be
> exercised consisted only of range partition key with columns all of the
> same type: create table test(a int, b int) partition by range (a, (a+b));
It doesn't seem like it; you had this: create table part1 partition of
range_parted for values from ('a', 1) to ('a', 10);
>> I recommend that once you fix this, you run 'make check' with #define
>> CLOBBER_CACHE_ALWAYS 1 and look for other hazards. Such mistakes are
>> easy to make with this kind of patch.
>
> With the attached latest version of the patches, I couldn't see any
> failures with a CLOBBER_CACHE_ALWAYS build.
Cool.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-12-07 16:19 Erik Rijkers <er@xs4all.nl>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 329+ messages in thread
From: Erik Rijkers @ 2016-12-07 16:19 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org
On 2016-12-07 12:42, Amit Langote wrote:
> 0001-Catalog-and-DDL-for-partitioned-tables-20.patch
> 0002-psql-and-pg_dump-support-for-partitioned-tables-20.patch
> 0003-Catalog-and-DDL-for-partitions-20.patch
> 0004-psql-and-pg_dump-support-for-partitions-20.patch
> 0005-Teach-a-few-places-to-use-partition-check-quals-20.patch
> 0006-Tuple-routing-for-partitioned-tables-20.patch
> 0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-20.patch
Patches apply, compile, check OK.
But this yields a segfault:
begin;
create schema if not exists s;
create table s.t (c text, d text, id serial) partition by list
((ascii(substring(coalesce(c, d, ''), 1, 1))));
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );
it logs as follows:
2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
terminated by signal 11: Segmentation fault
2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running:
create table s.t_part_ascii_065 partition of s.t for values in ( 65 );
2016-12-07 17:03:45.787 CET 6125 LOG: terminating any other active
server processes
2016-12-07 17:03:45.791 CET 6125 LOG: all server processes terminated;
reinitializing
2016-12-07 17:03:45.999 CET 11655 LOG: database system was interrupted;
last known up at 2016-12-07 17:00:38 CET
2016-12-07 17:03:48.040 CET 11655 LOG: database system was not properly
shut down; automatic recovery in progress
2016-12-07 17:03:48.156 CET 11655 LOG: redo starts at 0/2897988
2016-12-07 17:03:48.172 CET 11655 LOG: invalid magic number 0000 in log
segment 000000010000000000000002, offset 9207808
2016-12-07 17:03:48.172 CET 11655 LOG: redo done at 0/28C72C0
2016-12-07 17:03:48.172 CET 11655 LOG: last completed transaction was
at log time 2016-12-07 17:01:29.580562+01
2016-12-07 17:03:49.534 CET 11655 LOG: MultiXact member wraparound
protections are now enabled
2016-12-07 17:03:49.622 CET 6125 LOG: database system is ready to
accept connections
Thanks,
Erik Rijkers
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-12-07 16:34 Amit Langote <amitlangote09@gmail.com>
parent: Erik Rijkers <er@xs4all.nl>
0 siblings, 1 reply; 329+ messages in thread
From: Amit Langote @ 2016-12-07 16:34 UTC (permalink / raw)
To: Erik Rijkers <er@xs4all.nl>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Robert Haas <robertmhaas@gmail.com>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org
Hi Erik,
On Thu, Dec 8, 2016 at 1:19 AM, Erik Rijkers <er@xs4all.nl> wrote:
> On 2016-12-07 12:42, Amit Langote wrote:
>
>> 0001-Catalog-and-DDL-for-partitioned-tables-20.patch
>> 0002-psql-and-pg_dump-support-for-partitioned-tables-20.patch
>> 0003-Catalog-and-DDL-for-partitions-20.patch
>> 0004-psql-and-pg_dump-support-for-partitions-20.patch
>> 0005-Teach-a-few-places-to-use-partition-check-quals-20.patch
>> 0006-Tuple-routing-for-partitioned-tables-20.patch
>> 0007-Update-DDL-Partitioning-chapter-to-reflect-new-devel-20.patch
>
>
> Patches apply, compile, check OK.
Thanks!
> But this yields a segfault:
>
> begin;
> create schema if not exists s;
> create table s.t (c text, d text, id serial) partition by list
> ((ascii(substring(coalesce(c, d, ''), 1, 1))));
> create table s.t_part_ascii_065 partition of s.t for values in ( 65 );
>
> it logs as follows:
>
> 2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
> terminated by signal 11: Segmentation fault
> 2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running: create
> table s.t_part_ascii_065 partition of s.t for values in ( 65 );
Hm, will look into this in a few hours.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-12-07 16:38 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 329+ messages in thread
From: Robert Haas @ 2016-12-07 16:38 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Erik Rijkers <er@xs4all.nl>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org
On Wed, Dec 7, 2016 at 11:34 AM, Amit Langote <amitlangote09@gmail.com> wrote:
>> begin;
>> create schema if not exists s;
>> create table s.t (c text, d text, id serial) partition by list
>> ((ascii(substring(coalesce(c, d, ''), 1, 1))));
>> create table s.t_part_ascii_065 partition of s.t for values in ( 65 );
>>
>> it logs as follows:
>>
>> 2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
>> terminated by signal 11: Segmentation fault
>> 2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running: create
>> table s.t_part_ascii_065 partition of s.t for values in ( 65 );
>
> Hm, will look into this in a few hours.
My bad. The fix I sent last night for one of the cache flush issues
wasn't quite right. The attached seems to fix it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] partkey-flush-fix.patch (1.5K, ../../CA+Tgmob==oNYLN2M-pVVaLNMZxqvXk3_EoUnJqqRdd-8tm0CNw@mail.gmail.com/2-partkey-flush-fix.patch)
download | inline diff:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ae77d15..a230b20 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2516,6 +2516,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partkey;
bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
@@ -2547,6 +2548,7 @@ 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_partkey = (relation->rd_partkey != NULL);
keep_partdesc = equalPartitionDescs(relation->rd_partkey,
relation->rd_partdesc,
newrel->rd_partdesc);
@@ -2604,9 +2606,12 @@ RelationClearRelation(Relation relation, bool rebuild)
SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
- /* partition key must be preserved */
- SWAPFIELD(PartitionKey, rd_partkey);
- SWAPFIELD(MemoryContext, rd_partkeycxt);
+ /* partition key must be preserved, if we have one */
+ if (keep_partkey)
+ {
+ SWAPFIELD(PartitionKey, rd_partkey);
+ SWAPFIELD(MemoryContext, rd_partkeycxt);
+ }
/* preserve old partdesc if no logical change */
if (keep_partdesc)
{
^ permalink raw reply [nested|flat] 329+ messages in thread
* Re: Declarative partitioning - another take
@ 2016-12-07 16:53 Erik Rijkers <er@xs4all.nl>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 329+ messages in thread
From: Erik Rijkers @ 2016-12-07 16:53 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org
On 2016-12-07 17:38, Robert Haas wrote:
> On Wed, Dec 7, 2016 at 11:34 AM, Amit Langote <amitlangote09@gmail.com>
> wrote:
>>> begin;
>>> create schema if not exists s;
>>> create table s.t (c text, d text, id serial) partition by list
>>> ((ascii(substring(coalesce(c, d, ''), 1, 1))));
>>> create table s.t_part_ascii_065 partition of s.t for values in ( 65
>>> );
>>>
>>> it logs as follows:
>>>
>>> 2016-12-07 17:03:45.787 CET 6125 LOG: server process (PID 11503) was
>>> terminated by signal 11: Segmentation fault
>>> 2016-12-07 17:03:45.787 CET 6125 DETAIL: Failed process was running:
>>> create
>>> table s.t_part_ascii_065 partition of s.t for values in ( 65 );
>>
>> Hm, will look into this in a few hours.
>
> My bad. The fix I sent last night for one of the cache flush issues
> wasn't quite right. The attached seems to fix it.
Yes, fixed here too. Thanks.
> --
> Robert Haas
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 329+ messages in thread
165 further messages in this thread omitted from this page (render size limit).
Use the per-message pages, or the t.mbox.gz byte stream, for the complete thread.
end of thread, other threads:[~2017-06-29 11:28 UTC | newest]
Thread overview: 329+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2016-08-10 11:09 Declarative partitioning - another take Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-10 18:22 ` Robert Haas <robertmhaas@gmail.com>
2016-08-15 11:21 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-16 06:30 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-16 14:05 ` Robert Eckhardt <reckhardt@pivotal.io>
2016-09-19 20:06 ` Robert Haas <robertmhaas@gmail.com>
2016-08-16 18:54 ` Robert Haas <robertmhaas@gmail.com>
2016-08-16 20:29 ` Robert Haas <robertmhaas@gmail.com>
2016-08-17 05:33 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-17 06:21 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-17 06:27 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-17 20:23 ` Robert Haas <robertmhaas@gmail.com>
2016-08-26 08:03 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-29 11:53 ` Robert Haas <robertmhaas@gmail.com>
2016-08-31 07:07 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-31 07:17 ` Robert Haas <robertmhaas@gmail.com>
2016-08-31 07:35 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-06 12:19 ` Robert Haas <robertmhaas@gmail.com>
2016-09-06 13:07 ` Amit Langote <amitlangote09@gmail.com>
2016-09-07 08:56 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2016-09-07 10:28 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-08 12:38 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2016-09-09 01:43 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-26 08:15 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-06 07:13 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-06 08:07 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-06 11:15 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2016-09-06 13:04 ` Amit Langote <amitlangote09@gmail.com>
2016-09-09 08:55 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-15 08:53 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-15 12:58 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-27 08:39 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-27 09:09 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-27 11:19 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-15 16:37 ` Robert Haas <robertmhaas@gmail.com>
2016-09-18 06:41 ` Amit Langote <amitlangote09@gmail.com>
2016-09-20 13:41 ` Robert Haas <robertmhaas@gmail.com>
2016-09-29 12:09 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-30 00:10 ` Robert Haas <robertmhaas@gmail.com>
2016-10-03 04:26 ` Michael Paquier <michael.paquier@gmail.com>
2016-10-03 04:45 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-04 08:02 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-04 17:12 ` Robert Haas <robertmhaas@gmail.com>
2016-10-06 07:14 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-07 08:33 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2016-10-07 09:20 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-07 09:27 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-10-07 11:49 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-25 06:58 ` Amit Kapila <amit.kapila16@gmail.com>
2016-10-26 02:00 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-26 18:31 ` Robert Haas <robertmhaas@gmail.com>
2016-10-26 18:06 ` Robert Haas <robertmhaas@gmail.com>
2016-10-25 11:32 ` Amit Kapila <amit.kapila16@gmail.com>
2016-10-26 01:06 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-26 02:41 ` Amit Kapila <amit.kapila16@gmail.com>
2016-10-26 02:57 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-26 03:09 ` Amit Kapila <amit.kapila16@gmail.com>
2016-10-26 05:26 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-26 18:13 ` Robert Haas <robertmhaas@gmail.com>
2016-10-27 02:08 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-05 01:50 ` Robert Haas <robertmhaas@gmail.com>
2016-10-05 04:47 ` Petr Jelinek <petr@2ndquadrant.com>
2016-10-26 08:57 ` Amit Kapila <amit.kapila16@gmail.com>
2016-10-26 09:34 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-26 10:12 ` Amit Kapila <amit.kapila16@gmail.com>
2016-10-26 18:04 ` Robert Haas <robertmhaas@gmail.com>
2016-10-28 07:53 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-01 16:57 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 17:49 ` Corey Huinker <corey.huinker@gmail.com>
2016-11-01 18:01 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 18:11 ` Francisco Olarte <folarte@peoplecall.com>
2016-11-01 18:11 ` Corey Huinker <corey.huinker@gmail.com>
2016-11-01 18:24 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 18:36 ` Corey Huinker <corey.huinker@gmail.com>
2016-11-03 15:52 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 18:05 ` Francisco Olarte <folarte@peoplecall.com>
2016-11-01 18:09 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 18:18 ` Francisco Olarte <folarte@peoplecall.com>
2016-11-01 18:15 ` Corey Huinker <corey.huinker@gmail.com>
2016-11-01 17:34 ` Robert Haas <robertmhaas@gmail.com>
2016-11-02 07:41 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-02 08:32 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-03 16:37 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 17:44 ` Robert Haas <robertmhaas@gmail.com>
2016-11-02 10:47 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-03 15:47 ` Robert Haas <robertmhaas@gmail.com>
2016-11-01 17:53 ` Robert Haas <robertmhaas@gmail.com>
2016-11-02 02:55 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-03 11:46 ` alvherre@alvh.no-ip.org
2016-11-03 15:49 ` Robert Haas <robertmhaas@gmail.com>
2016-11-14 06:31 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-04 00:16 ` Robert Haas <robertmhaas@gmail.com>
2016-11-09 11:14 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-09 17:00 ` Robert Haas <robertmhaas@gmail.com>
2016-11-09 17:10 ` Robert Haas <robertmhaas@gmail.com>
2016-11-10 02:58 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-10 21:51 ` Robert Haas <robertmhaas@gmail.com>
2016-11-11 10:30 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-11 11:49 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-15 09:51 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-15 10:30 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-15 19:21 ` Robert Haas <robertmhaas@gmail.com>
2016-11-17 11:27 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-17 16:43 ` Robert Haas <robertmhaas@gmail.com>
2016-11-18 01:18 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-18 16:39 ` Robert Haas <robertmhaas@gmail.com>
2017-05-10 10:50 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2017-05-10 13:19 ` Robert Haas <robertmhaas@gmail.com>
2016-11-17 19:14 ` Robert Haas <robertmhaas@gmail.com>
2016-11-18 10:59 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-18 17:23 ` Robert Haas <robertmhaas@gmail.com>
2016-11-22 09:15 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-22 13:11 ` Rushabh Lathia <rushabh.lathia@gmail.com>
2016-11-24 10:34 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-28 14:42 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-30 10:53 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-22 19:50 ` Robert Haas <robertmhaas@gmail.com>
2016-11-24 11:13 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-25 02:44 ` Robert Haas <robertmhaas@gmail.com>
2016-11-25 10:49 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-30 15:48 ` Robert Haas <robertmhaas@gmail.com>
2016-11-30 15:56 ` Amit Langote <amitlangote09@gmail.com>
2016-12-07 04:38 ` Robert Haas <robertmhaas@gmail.com>
2016-12-07 11:42 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-07 14:45 ` Robert Haas <robertmhaas@gmail.com>
2016-12-07 16:19 ` Erik Rijkers <er@xs4all.nl>
2016-12-07 16:34 ` Amit Langote <amitlangote09@gmail.com>
2016-12-07 16:38 ` Robert Haas <robertmhaas@gmail.com>
2016-12-07 16:53 ` Erik Rijkers <er@xs4all.nl>
2016-12-07 18:20 ` Robert Haas <robertmhaas@gmail.com>
2016-12-08 02:25 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-08 13:35 ` Stephen Frost <sfrost@snowman.net>
2016-12-09 02:31 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-08 04:39 ` Andres Freund <andres@anarazel.de>
2016-12-08 04:42 ` Michael Paquier <michael.paquier@gmail.com>
2016-12-08 15:25 ` Robert Haas <robertmhaas@gmail.com>
2016-12-08 16:13 ` Dmitry Ivanov <d.ivanov@postgrespro.ru>
2016-12-08 16:29 ` Robert Haas <robertmhaas@gmail.com>
2016-12-08 16:43 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2016-12-08 16:53 ` Robert Haas <robertmhaas@gmail.com>
2016-12-09 01:09 ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2016-12-09 01:19 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-09 01:27 ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2016-12-08 16:44 ` Dmitry Ivanov <d.ivanov@postgrespro.ru>
2016-12-08 16:54 ` Robert Haas <robertmhaas@gmail.com>
2016-12-12 17:14 ` Dmitry Ivanov <d.ivanov@postgrespro.ru>
2016-12-12 17:45 ` Dmitry Ivanov <d.ivanov@postgrespro.ru>
2016-12-13 06:58 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-13 16:32 ` Robert Haas <robertmhaas@gmail.com>
2016-12-16 08:02 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-16 08:38 ` Greg Stark <stark@mit.edu>
2016-12-16 08:58 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-16 08:42 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-16 16:07 ` Robert Haas <robertmhaas@gmail.com>
2016-12-17 02:32 ` Amit Langote <amitlangote09@gmail.com>
2016-12-19 03:00 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-20 03:59 ` Robert Haas <robertmhaas@gmail.com>
2016-12-20 04:43 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-20 09:51 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2016-12-20 14:17 ` Robert Haas <robertmhaas@gmail.com>
2016-12-20 15:27 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2016-12-20 15:45 ` Robert Haas <robertmhaas@gmail.com>
2016-12-20 16:45 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2016-12-21 02:14 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-21 04:42 ` Robert Haas <robertmhaas@gmail.com>
2016-12-21 05:03 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-21 05:20 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-20 16:53 ` Robert Haas <robertmhaas@gmail.com>
2016-12-21 10:33 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-21 16:50 ` Robert Haas <robertmhaas@gmail.com>
2016-12-22 08:35 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-22 23:08 ` Robert Haas <robertmhaas@gmail.com>
2016-12-26 10:46 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-27 08:59 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-27 09:30 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2016-12-27 09:48 ` 高增琦 <pgf00a@gmail.com>
2016-12-27 10:07 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-28 01:41 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-04 18:26 ` Robert Haas <robertmhaas@gmail.com>
2017-01-06 11:23 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-09 08:29 ` amul sul <sulamul@gmail.com>
2017-01-10 05:44 ` Keith Fiske <keith@omniti.com>
2017-01-10 09:17 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-10 09:07 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-12 03:53 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-27 09:54 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-03 10:04 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2017-01-04 05:07 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-04 06:11 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2017-01-04 07:31 ` 高增琦 <pgf00a@gmail.com>
2017-01-04 08:02 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-04 20:50 ` Robert Haas <robertmhaas@gmail.com>
2017-01-10 11:06 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-13 21:24 ` Robert Haas <robertmhaas@gmail.com>
2017-01-16 09:09 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-17 16:54 ` Robert Haas <robertmhaas@gmail.com>
2017-01-18 20:12 ` Robert Haas <robertmhaas@gmail.com>
2017-01-18 20:29 ` Robert Haas <robertmhaas@gmail.com>
2017-01-19 05:15 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-19 11:23 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-19 19:18 ` Robert Haas <robertmhaas@gmail.com>
2017-01-20 02:58 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-20 03:40 ` Keith Fiske <keith@omniti.com>
2017-01-25 01:02 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-24 17:56 ` Robert Haas <robertmhaas@gmail.com>
2017-01-25 01:33 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-24 20:55 ` Robert Haas <robertmhaas@gmail.com>
2017-01-25 01:41 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-25 05:54 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2017-01-25 07:48 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-30 21:42 ` Peter Eisentraut <peter.eisentraut@2ndquadrant.com>
2017-02-03 02:31 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-02-03 23:49 ` Robert Haas <robertmhaas@gmail.com>
2017-02-08 12:20 ` amul sul <sulamul@gmail.com>
2017-02-09 01:33 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-02-09 06:22 ` amul sul <sulamul@gmail.com>
2017-02-10 05:54 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-02-14 17:38 ` Robert Haas <robertmhaas@gmail.com>
2017-01-20 06:15 ` Andres Freund <andres@anarazel.de>
2017-01-20 07:20 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-20 21:29 ` Robert Haas <robertmhaas@gmail.com>
2017-01-23 05:47 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-02-23 07:17 ` Yugo Nagata <nagata@sraoss.co.jp>
2017-02-23 07:29 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-02-23 12:53 ` Yugo Nagata <nagata@sraoss.co.jp>
2017-01-04 18:18 ` Robert Haas <robertmhaas@gmail.com>
2016-12-15 15:40 ` Dmitry Ivanov <d.ivanov@postgrespro.ru>
2016-12-16 09:04 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-13 17:22 ` Ildar Musin <i.musin@postgrespro.ru>
2016-12-13 18:10 ` Robert Haas <robertmhaas@gmail.com>
2016-12-14 12:25 ` Ildar Musin <i.musin@postgrespro.ru>
2016-12-09 04:58 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-09 06:16 ` Venkata B Nagothi <nag1010@gmail.com>
2016-12-09 12:11 ` Amit Langote <amitlangote09@gmail.com>
2016-12-11 01:02 ` Venkata B Nagothi <nag1010@gmail.com>
2016-12-12 04:06 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-14 03:14 ` Venkata B Nagothi <nag1010@gmail.com>
2017-01-05 08:10 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-01-05 17:16 ` Keith Fiske <keith@omniti.com>
2017-01-06 07:56 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-13 15:39 ` Robert Haas <robertmhaas@gmail.com>
2016-12-09 10:46 ` Maksim Milyutin <m.milyutin@postgrespro.ru>
2016-12-13 15:44 ` Robert Haas <robertmhaas@gmail.com>
2016-12-14 07:20 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2017-04-07 10:05 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2017-04-07 10:52 ` Maksim Milyutin <m.milyutin@postgrespro.ru>
2017-04-21 08:00 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2017-04-24 10:43 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-25 18:58 ` Robert Haas <robertmhaas@gmail.com>
2017-04-26 02:34 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-26 16:52 ` Robert Haas <robertmhaas@gmail.com>
2017-04-27 01:30 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-27 20:24 ` Robert Haas <robertmhaas@gmail.com>
2017-04-28 06:13 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-28 16:25 ` Robert Haas <robertmhaas@gmail.com>
2017-05-01 04:18 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-05-01 12:30 ` Robert Haas <robertmhaas@gmail.com>
2017-05-02 07:30 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-05-02 17:48 ` Robert Haas <robertmhaas@gmail.com>
2017-05-08 00:47 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-05-08 01:22 ` Thomas Munro <thomas.munro@enterprisedb.com>
2017-05-08 01:44 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-05-10 03:51 ` Robert Haas <robertmhaas@gmail.com>
2017-05-10 03:54 ` Thomas Munro <thomas.munro@enterprisedb.com>
2017-05-10 03:59 ` Robert Haas <robertmhaas@gmail.com>
2017-05-10 05:13 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-27 22:36 ` David Fetter <david@fetter.org>
2017-04-28 09:29 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-28 21:56 ` David Fetter <david@fetter.org>
2017-05-01 04:51 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-30 08:00 ` Noah Misch <noah@leadboat.com>
2017-06-29 11:28 ` Etsuro Fujita <fujita.etsuro@lab.ntt.co.jp>
2016-12-08 08:57 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-12 01:02 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2016-12-12 06:37 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-12 15:17 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2016-12-13 08:45 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-14 14:54 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2016-12-12 14:14 ` Peter Eisentraut <peter.eisentraut@2ndquadrant.com>
2016-12-13 01:17 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-12-08 02:03 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-24 05:35 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-24 05:39 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-24 06:04 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-24 06:10 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-24 06:32 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-24 06:40 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-24 19:36 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2016-11-25 01:08 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-25 03:11 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2016-11-25 04:51 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-11-25 09:16 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-29 11:24 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-30 15:54 ` Robert Haas <robertmhaas@gmail.com>
2016-11-10 12:40 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-10 19:04 ` Robert Haas <robertmhaas@gmail.com>
2016-11-14 05:16 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-07 17:15 ` Jaime Casanova <jaime.casanova@2ndquadrant.com>
2016-11-07 17:24 ` Jaime Casanova <jaime.casanova@2ndquadrant.com>
2016-11-08 04:08 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-11-08 06:42 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-10-05 07:57 ` Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2016-10-05 08:59 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-22 05:42 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-22 07:32 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-22 10:10 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-27 05:27 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-27 06:44 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-27 09:16 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-27 09:18 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-26 07:49 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-06 12:24 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-22 04:51 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-25 06:52 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-25 07:15 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-01 05:17 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-01 08:17 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-02 05:38 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-02 06:08 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-02 06:22 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-02 06:53 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-02 06:57 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-09-02 07:07 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-09-14 07:58 Re: Declarative partitioning - another take Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2017-04-25 10:03 Re: Declarative partitioning - another take Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2017-04-26 02:43 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2017-04-26 09:17 ` Amit Khandekar <amitdkhan.pg@gmail.com>
2017-04-28 08:11 Re: Declarative partitioning - another take Rajkumar Raghuwanshi <rajkumar.raghuwanshi@enterprisedb.com>
2017-04-28 08:50 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
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