0003-Catalog-and-DDL-for-partitions-10.patch
text/x-diff
Filename: 0003-Catalog-and-DDL-for-partitions-10.patch
Type: text/x-diff
Part: 2
Patch
Format: unified
Series: patch 0003
| File | + | − |
|---|---|---|
| doc/src/sgml/catalogs.sgml | 17 | 0 |
| doc/src/sgml/ref/alter_table.sgml | 99 | 2 |
| doc/src/sgml/ref/create_foreign_table.sgml | 26 | 0 |
| doc/src/sgml/ref/create_table.sgml | 91 | 1 |
| src/backend/catalog/heap.c | 68 | 4 |
| src/backend/catalog/Makefile | 1 | 1 |
| src/backend/catalog/partition.c | 1702 | 0 |
| src/backend/commands/sequence.c | 1 | 0 |
| src/backend/commands/tablecmds.c | 639 | 112 |
| src/backend/nodes/copyfuncs.c | 51 | 0 |
| src/backend/nodes/equalfuncs.c | 45 | 0 |
| src/backend/nodes/nodeFuncs.c | 6 | 0 |
| src/backend/nodes/outfuncs.c | 30 | 0 |
| src/backend/nodes/readfuncs.c | 36 | 0 |
| src/backend/parser/gram.y | 235 | 3 |
| src/backend/parser/parse_agg.c | 0 | 1 |
| src/backend/parser/parse_utilcmd.c | 357 | 1 |
| src/backend/utils/cache/relcache.c | 99 | 6 |
| src/include/catalog/heap.h | 1 | 0 |
| src/include/catalog/partition.h | 58 | 0 |
| src/include/catalog/pg_class.h | 13 | 9 |
| src/include/nodes/nodes.h | 3 | 0 |
| src/include/nodes/parsenodes.h | 51 | 2 |
| src/include/parser/kwlist.h | 3 | 0 |
| src/include/utils/rel.h | 21 | 0 |
| src/test/regress/expected/alter_table.out | 228 | 0 |
| src/test/regress/expected/create_table.out | 200 | 0 |
| src/test/regress/sql/alter_table.sql | 193 | 0 |
| src/test/regress/sql/create_table.sql | 155 | 0 |
>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