Implement table partitioning.

Table partitioning is like table inheritance and reuses much of the
existing infrastructure, but there are some important differences.
The parent is called a partitioned table and is always empty; it may
not have indexes or non-inherited constraints, since those make no
sense for a relation with no data of its own.  The children are called
partitions and contain all of the actual data.  Each partition has an
implicit partitioning constraint.  Multiple inheritance is not
allowed, and partitioning and inheritance can't be mixed.  Partitions
can't have extra columns and may not allow nulls unless the parent
does.  Tuples inserted into the parent are automatically routed to the
correct partition, so tuple-routing ON INSERT triggers are not needed.
Tuple routing isn't yet supported for partitions which are foreign
tables, and it doesn't handle updates that cross partition boundaries.

Currently, tables can be range-partitioned or list-partitioned.  List
partitioning is limited to a single column, but range partitioning can
involve multiple columns.  A partitioning "column" can be an
expression.

Because table partitioning is less general than table inheritance, it
is hoped that it will be easier to reason about properties of
partitions, and therefore that this will serve as a better foundation
for a variety of possible optimizations, including query planner
optimizations.  The tuple routing based which this patch does based on
the implicit partitioning constraints is an example of this, but it
seems likely that many other useful optimizations are also possible.

Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat,
Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova,
Rushabh Lathia, Erik Rijkers, among others.  Minor revisions by me.
This commit is contained in:
Robert Haas 2016-12-07 13:17:43 -05:00
parent b7e1ae2328
commit f0e44751d7
85 changed files with 8896 additions and 281 deletions

View File

@ -225,6 +225,11 @@
<entry>template data for procedural languages</entry>
</row>
<row>
<entry><link linkend="catalog-pg-partitioned-table"><structname>pg_partitioned_table</structname></link></entry>
<entry>information about partition key of tables</entry>
</row>
<row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
@ -1723,7 +1728,8 @@
<entry><type>char</type></entry>
<entry></entry>
<entry>
<literal>r</> = ordinary table, <literal>i</> = index,
<literal>r</> = ordinary table, <literal>P</> = partitioned table,
<literal>i</> = index
<literal>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table,
@ -1839,6 +1845,13 @@
</entry>
</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>
@ -1885,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>
@ -4689,6 +4712,110 @@
</sect1>
<sect1 id="catalog-pg-partitioned-table">
<title><structname>pg_partitioned_table</structname></title>
<indexterm zone="catalog-pg-partitioned-table">
<primary>pg_partitioned_table</primary>
</indexterm>
<para>
The catalog <structname>pg_partitioned_table</structname> stores
information about how tables are partitioned.
</para>
<table>
<title><structname>pg_partitioned_table</> Columns</title>
<tgroup cols="4">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>References</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><structfield>partrelid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
<entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
</row>
<row>
<entry><structfield>partstrat</structfield></entry>
<entry><type>char</type></entry>
<entry></entry>
<entry>
Partitioning strategy; <literal>l</> = list partitioned table,
<literal>r</> = range partitioned table
</entry>
</row>
<row>
<entry><structfield>partnatts</structfield></entry>
<entry><type>int2</type></entry>
<entry></entry>
<entry>The number of columns in partition key</entry>
</row>
<row>
<entry><structfield>partattrs</structfield></entry>
<entry><type>int2vector</type></entry>
<entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
<entry>
This is an array of <structfield>partnatts</structfield> values that
indicate which table columns are part of the partition key. For
example, a value of <literal>1 3</literal> would mean that the first
and the third table columns make up the partition key. A zero in this
array indicates that the corresponding partition key column is an
expression, rather than a simple column reference.
</entry>
</row>
<row>
<entry><structfield>partclass</structfield></entry>
<entry><type>oidvector</type></entry>
<entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
<entry>
For each column in the partition key, this contains the OID of the
operator class to use. See
<link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
</entry>
</row>
<row>
<entry><structfield>partcollation</structfield></entry>
<entry><type>oidvector</type></entry>
<entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
<entry>
For each column in the partition key, this contains the OID of the
the collation to use for partitioning.
</entry>
</row>
<row>
<entry><structfield>partexprs</structfield></entry>
<entry><type>pg_node_tree</type></entry>
<entry></entry>
<entry>
Expression trees (in <function>nodeToString()</function>
representation) for partition key columns that are not simple column
references. This is a list with one element for each zero
entry in <structfield>partattrs</>. Null if all partition key columns
are simple references.
</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>

View File

@ -33,6 +33,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase>
@ -166,6 +170,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
values or to reject null values. You can only use <literal>SET
NOT NULL</> when the column contains no null values.
</para>
<para>
If this table is a partition, one cannot perform <literal>DROP NOT NULL</>
on a column if it is marked <literal>NOT NULL</literal> in the parent
table.
</para>
</listitem>
</varlistentry>
@ -704,13 +714,63 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ATTACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
<listitem>
<para>
This form attaches an existing table (which might itself be partitioned)
as a partition of the target table using the same syntax for
<replaceable class="PARAMETER">partition_bound_spec</replaceable> as
<xref linkend="sql-createtable">. The partition bound specification
must correspond to the partitioning strategy and partition key of the
target table. The table to be attached must have all the same columns
as the target table and no more; moreover, the column types must also
match. Also, it must have all the <literal>NOT NULL</literal> and
<literal>CHECK</literal> constraints of the target table. Currently
<literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
<literal>FOREIGN KEY</literal> constraints are not considered.
If any of the <literal>CHECK</literal> constraints of the table being
attached is marked <literal>NO INHERIT</literal>, the command will fail;
such a constraint must be recreated without the <literal>NO INHERIT</literal>
clause.
</para>
<para>
A full table scan is performed on the table being attached to check that
no existing row in the table violates the partition constraint. It is
possible to avoid this scan by adding a valid <literal>CHECK</literal>
constraint to the table that would allow only the rows satisfying the
desired partition constraint before running this command. It will be
determined using such a constraint that the table need not be scanned
to validate the partition constraint. This does not work, however, if
any of the partition keys is an expression and the partition does not
accept <literal>NULL</literal> values. If attaching a list partition
that will not accept <literal>NULL</literal> values, also add
<literal>NOT NULL</literal> constraint to the partition key column,
unless it's an expression.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DETACH PARTITION</literal> <replaceable class="PARAMETER">partition_name</replaceable></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>
<para>
All the actions except <literal>RENAME</literal>,
<literal>SET TABLESPACE</literal> and <literal>SET SCHEMA</literal>
can be combined into
<literal>SET TABLESPACE</literal>, <literal>SET SCHEMA</literal>,
<literal>ATTACH PARTITION</literal>, and
<literal>DETACH PARTITION</literal> can be combined into
a list of multiple alterations to apply in parallel. For example, it
is possible to add several columns and/or alter the type of several
columns in a single command. This is particularly useful with large
@ -721,8 +781,9 @@ 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, you must own the parent
table as well. Also, to attach a table as a new partition of the table,
you must own the table being attached.
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 +999,25 @@ 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. Refer to
<xref linkend="sql-createtable"> for more details on the syntax of the same.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -977,6 +1057,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
but does not require a table rewrite.
</para>
<para>
Similarly, when attaching a new partition it may be scanned to verify that
existing rows meet the partition constraint.
</para>
<para>
The main reason for providing the option to specify multiple changes
in a single <command>ALTER TABLE</> is that multiple table scans or
@ -1047,6 +1132,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 +1321,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
</programlisting></para>
<para>
Attach a partition to range partitioned table:
<programlisting>
ALTER TABLE measurement
ATTACH PARTITION measurement_y2016m07 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
</programlisting></para>
<para>
Attach a partition to list partitioned table:
<programlisting>
ALTER TABLE cities
ATTACH PARTITION cities_west FOR VALUES IN ('Los Angeles', 'San Francisco');
</programlisting></para>
<para>
Detach a partition from partitioned table:
<programlisting>
ALTER TABLE cities
DETACH PARTITION measurement_y2015m12;
</programlisting></para>
</refsect1>
<refsect1>

View File

@ -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> ]
@ -67,6 +76,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
name as any existing data type in the same schema.
</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>
@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
SERVER film_server;
</programlisting></para>
<para>
Create foreign table <structname>measurement_y2016m07</>, which will be
accessed through the server <structname>server_07</>, as a partition
of the range partitioned table <structname>measurement</>:
<programlisting>
CREATE FOREIGN TABLE measurement_y2016m07
PARTITION OF measurement FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')
SERVER server_07;
</programlisting></para>
</refsect1>
<refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">

View File

@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@ -38,6 +39,18 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</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> ]
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> [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</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> ]
@ -70,6 +83,11 @@ 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>
{ IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) |
FROM ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) TO ( { <replaceable class="PARAMETER">expression</replaceable> | UNBOUNDED } [, ...] ) }
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@ -229,6 +247,51 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
<varlistentry>
<term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
<listitem>
<para>
Creates the table as <firstterm>partition</firstterm> of the specified
parent table.
</para>
<para>
The partition bound specification must correspond to the partitioning
method and partition key of the parent table, and must not overlap with
any existing partition of that parent.
</para>
<para>
A partition cannot have columns other than those inherited from the
parent. That includes the <structfield>oid</> column, which can be
specified using the <literal>WITH (OIDS)</literal> clause.
Defaults and constraints can optionally be specified for each of the
inherited columns. One can also specify table constraints in addition
to those inherited from the parent. If a check constraint with the name
matching one of the parent's constraint is specified, it is merged with
the latter, provided the specified condition is same.
</para>
<para>
Rows inserted into a partitioned table will be automatically routed to
the correct partition. If no suitable partition exists, an error will
occur.
</para>
<para>
A partition must have the same column names and types as the table of
which it is a partition. Therefore, modifications to the column names
or types of the partitioned table will automatically propagate to all
children, as will operations such as TRUNCATE which normally affect a
table and all of its inheritance children. It is also possible to
TRUNCATE a partition individually, just as for an inheritance child.
Note that dropping a partition with <literal>DROP TABLE</literal>
requires taking an <literal>ACCESS EXCLUSIVE</literal> lock on the
parent table.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
@ -313,6 +376,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem>
</varlistentry>
<varlistentry>
<term><literal>PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
<listitem>
<para>
The optional <literal>PARTITION BY</literal> clause specifies a strategy
of partitioning the table. The table thus created is called a
<firstterm>partitioned</firstterm> table. The parenthesized list of
columns or expressions forms the <firstterm>partition key</firstterm>
for the table. When using range partitioning, the partition key can
include multiple columns or expressions, but for list partitioning, the
partition key must consist of a single column or expression. If no
btree operator class is specified when creating a partitioned table,
the default btree operator class for the datatype will be used. If
there is none, an error will be reported.
</para>
<para>
A partitioned table is divided into sub-tables (called partitions),
which are created using separate <literal>CREATE TABLE</> commands.
The partitioned table is itself empty. A data row inserted into the
table is routed to a partition based on the value of columns or
expressions in the partition key. If no existing partition matches
the values in the new row, an error will be reported.
</para>
<para>
Partitioned tables do not support <literal>UNIQUE</literal>,
<literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
<literal>FOREIGN KEY</literal> constraints; however, you can define
these constraints on individual partitions.
</para>
<para>
When using range partitioning, a <literal>NOT NULL</literal> constraint
is added to each non-expression column in the partition key.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
@ -1368,6 +1471,57 @@ CREATE TABLE employees OF employee_type (
PRIMARY KEY (name),
salary WITH OPTIONS DEFAULT 1000
);
</programlisting></para>
<para>
Create a range partitioned table:
<programlisting>
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
) PARTITION BY RANGE (logdate);
</programlisting></para>
<para>
Create a list partitioned table:
<programlisting>
CREATE TABLE cities (
name text not null,
population int,
) PARTITION BY LIST (initcap(name));
</programlisting></para>
<para>
Create partition of a range partitioned table:
<programlisting>
CREATE TABLE measurement_y2016m07
PARTITION OF measurement (
unitsales WITH OPTIONS DEFAULT 0
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01');
</programlisting></para>
<para>
Create partition of a list partitioned table:
<programlisting>
CREATE TABLE cities_west
PARTITION OF cities (
CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco');
</programlisting></para>
<para>
Create partition of a list partitioned table that is itself further
partitioned and then add a partition to it:
<programlisting>
CREATE TABLE cities_west
PARTITION OF cities (
CONSTRAINT city_id_nonzero CHECK (city_id != 0)
) FOR VALUES IN ('Los Angeles', 'San Francisco') PARTITION BY RANGE (population);
CREATE TABLE cities_west_10000_to_100000
PARTITION OF cities_west FOR VALUES FROM (10000) TO (100000);
</programlisting></para>
</refsect1>

View File

@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */

View File

@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
pg_collation.h pg_range.h pg_transform.h \
pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \
toasting.h indexing.h \
)

View File

@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
objects = list_concat(objects, objs);
break;
case ACL_OBJECT_SEQUENCE:
objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);

View File

@ -1352,7 +1352,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
DependencyType self_behavior)
DependencyType self_behavior,
bool ignore_self)
{
find_expr_references_context context;
RangeTblEntry rte;
@ -1407,9 +1408,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs;
/* Record the self-dependencies */
recordMultipleDependencies(depender,
self_addrs->refs, self_addrs->numrefs,
self_behavior);
if (!ignore_self)
recordMultipleDependencies(depender,
self_addrs->refs, self_addrs->numrefs,
self_behavior);
free_object_addresses(self_addrs);
}

View File

@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@ -48,6 +49,8 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@ -808,6 +811,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
@ -819,6 +823,9 @@ InsertPgClassTuple(Relation pg_class_desc,
else
nulls[Anum_pg_class_reloptions - 1] = true;
/* relpartbound is set by updating this tuple, if necessary */
nulls[Anum_pg_class_relpartbound - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);
/*
@ -924,6 +931,9 @@ AddNewRelationTuple(Relation pg_class_desc,
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
/* relispartition is always set by updating this tuple later */
new_rel_reltup->relispartition = false;
new_rel_desc->rd_att->tdtypeid = new_type_oid;
/* Now build and insert the tuple */
@ -1104,7 +1114,8 @@ heap_create_with_catalog(const char *relname,
if (IsBinaryUpgrade &&
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE ||
relkind == RELKIND_PARTITIONED_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@ -1138,6 +1149,7 @@ heap_create_with_catalog(const char *relname,
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
relnamespace);
break;
@ -1182,7 +1194,8 @@ heap_create_with_catalog(const char *relname,
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
relkind == RELKIND_COMPOSITE_TYPE))
relkind == RELKIND_COMPOSITE_TYPE ||
relkind == RELKIND_PARTITIONED_TABLE))
new_array_oid = AssignTypeArrayOid();
/*
@ -1349,7 +1362,9 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
relkind == RELKIND_TOASTVALUE);
relkind == RELKIND_TOASTVALUE ||
relkind == RELKIND_PARTITIONED_TABLE);
heap_create_init_fork(new_rel_desc);
}
@ -1754,12 +1769,29 @@ void
heap_drop_with_catalog(Oid relid)
{
Relation rel;
Oid parentOid;
Relation parent = NULL;
/*
* Open and lock the relation.
*/
rel = relation_open(relid, AccessExclusiveLock);
/*
* If the relation is a partition, we must grab exclusive lock on its
* parent because we need to update its partition descriptor. We must
* take a table lock strong enough to prevent all queries on the parent
* from proceeding until we commit and send out a shared-cache-inval
* notice that will make them update their partition descriptor.
* Sometimes, doing this is cycles spent uselessly, especially if the
* parent will be dropped as part of the same command anyway.
*/
if (rel->rd_rel->relispartition)
{
parentOid = get_partition_parent(relid);
parent = heap_open(parentOid, AccessExclusiveLock);
}
/*
* There can no longer be anyone *else* touching the relation, but we
* might still have open queries or cursors, or pending trigger events, in
@ -1795,6 +1827,12 @@ heap_drop_with_catalog(Oid relid)
heap_close(rel, RowExclusiveLock);
}
/*
* If a partitioned table, delete the pg_partitioned_table tuple.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
RemovePartitionKeyByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
*/
@ -1845,6 +1883,12 @@ heap_drop_with_catalog(Oid relid)
* delete relation tuple
*/
DeleteRelationTuple(relid);
if (parent)
{
CacheInvalidateRelcache(parent);
heap_close(parent, NoLock); /* keep the lock */
}
}
@ -2027,6 +2071,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
else
attNos = NULL;
/*
* Partitioned tables do not contain any rows themselves, so a NO INHERIT
* constraint makes no sense.
*/
if (is_no_inherit &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
RelationGetRelationName(rel))));
/*
* Create the Check Constraint
*/
@ -2440,8 +2495,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)
@ -2486,10 +2544,24 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
tup = heap_copytuple(tup);
con = (Form_pg_constraint) GETSTRUCT(tup);
if (is_local)
con->conislocal = true;
/*
* In case of partitions, an inherited constraint must be
* inherited only once since it cannot have multiple parents and
* it is never considered local.
*/
if (rel->rd_rel->relispartition)
{
con->coninhcount = 1;
con->conislocal = false;
}
else
con->coninhcount++;
{
if (is_local)
con->conislocal = true;
else
con->coninhcount++;
}
if (is_no_inherit)
{
Assert(is_local);
@ -3013,3 +3085,187 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
/*
* StorePartitionKey
* Store information about the partition key rel into the catalog
*/
void
StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
AttrNumber *partattrs,
List *partexprs,
Oid *partopclass,
Oid *partcollation)
{
int i;
int2vector *partattrs_vec;
oidvector *partopclass_vec;
oidvector *partcollation_vec;
Datum partexprDatum;
Relation pg_partitioned_table;
HeapTuple tuple;
Datum values[Natts_pg_partitioned_table];
bool nulls[Natts_pg_partitioned_table];
ObjectAddress myself;
ObjectAddress referenced;
Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
tuple = SearchSysCache1(PARTRELID,
ObjectIdGetDatum(RelationGetRelid(rel)));
/* Copy the partition attribute numbers, opclass OIDs into arrays */
partattrs_vec = buildint2vector(partattrs, partnatts);
partopclass_vec = buildoidvector(partopclass, partnatts);
partcollation_vec = buildoidvector(partcollation, partnatts);
/* Convert the expressions (if any) to a text datum */
if (partexprs)
{
char *exprString;
exprString = nodeToString(partexprs);
partexprDatum = CStringGetTextDatum(exprString);
pfree(exprString);
}
else
partexprDatum = (Datum) 0;
pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
MemSet(nulls, false, sizeof(nulls));
/* Only this can ever be NULL */
if (!partexprDatum)
nulls[Anum_pg_partitioned_table_partexprs - 1] = true;
values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum;
tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls);
simple_heap_insert(pg_partitioned_table, tuple);
/* Update the indexes on pg_partitioned_table */
CatalogUpdateIndexes(pg_partitioned_table, tuple);
heap_close(pg_partitioned_table, RowExclusiveLock);
/* Mark this relation as dependent on a few things as follows */
myself.classId = RelationRelationId;
myself.objectId = RelationGetRelid(rel);;
myself.objectSubId = 0;
/* Operator class and collation per key column */
for (i = 0; i < partnatts; i++)
{
referenced.classId = OperatorClassRelationId;
referenced.objectId = partopclass[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
referenced.classId = CollationRelationId;
referenced.objectId = partcollation[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/*
* Anything mentioned in the expressions. We must ignore the column
* references, which will depend on the table itself; there is no
* separate partition key object.
*/
if (partexprs)
recordDependencyOnSingleRelExpr(&myself,
(Node *) partexprs,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
DEPENDENCY_AUTO, true);
/*
* We must invalidate the relcache so that the next
* CommandCounterIncrement() will cause the same to be rebuilt using the
* information in just created catalog entry.
*/
CacheInvalidateRelcache(rel);
}
/*
* RemovePartitionKeyByRelId
* Remove pg_partitioned_table entry for a relation
*/
void
RemovePartitionKeyByRelId(Oid relid)
{
Relation rel;
HeapTuple tuple;
rel = heap_open(PartitionedRelationId, RowExclusiveLock);
tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for partition key of relation %u",
relid);
simple_heap_delete(rel, &tuple->t_self);
ReleaseSysCache(tuple);
heap_close(rel, RowExclusiveLock);
}
/*
* StorePartitionBound
* Update pg_class tuple of rel to store the partition bound and set
* relispartition to true
*/
void
StorePartitionBound(Relation rel, Node *bound)
{
Relation classRel;
HeapTuple tuple,
newtuple;
Datum new_val[Natts_pg_class];
bool new_null[Natts_pg_class],
new_repl[Natts_pg_class];
/* Update pg_class tuple */
classRel = heap_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy1(RELOID,
ObjectIdGetDatum(RelationGetRelid(rel)));
#ifdef USE_ASSERT_CHECKING
{
Form_pg_class classForm;
bool isnull;
classForm = (Form_pg_class) GETSTRUCT(tuple);
Assert(!classForm->relispartition);
(void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
&isnull);
Assert(isnull);
}
#endif
/* Fill in relpartbound value */
memset(new_val, 0, sizeof(new_val));
memset(new_null, false, sizeof(new_null));
memset(new_repl, false, sizeof(new_repl));
new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(bound));
new_null[Anum_pg_class_relpartbound - 1] = false;
new_repl[Anum_pg_class_relpartbound - 1] = true;
newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
new_val, new_null, new_repl);
/* Also set the flag */
((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true;
simple_heap_update(classRel, &newtuple->t_self, newtuple);
CatalogUpdateIndexes(classRel, newtuple);
heap_freetuple(newtuple);
heap_close(classRel, RowExclusiveLock);
}

View File

@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
DEPENDENCY_AUTO);
DEPENDENCY_AUTO, false);
}
/* Store dependencies on anything mentioned in predicate */
@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
DEPENDENCY_AUTO);
DEPENDENCY_AUTO, false);
}
}
else

View File

@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
if (relation->rd_rel->relkind != RELKIND_RELATION)
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"),
relname);
break;
@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:

File diff suppressed because it is too large Load Diff

View File

@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
DEPENDENCY_NORMAL);
DEPENDENCY_NORMAL, false);
}
/* Post creation hook for new constraint */

View File

@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
onerel->rd_rel->relkind == RELKIND_MATVIEW ||
onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
childrel->rd_rel->relkind == RELKIND_MATVIEW ||
childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Regular table, so use the regular row acquisition function */
acquirefunc = acquire_sample_rows;

View File

@ -161,6 +161,11 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
PartitionDispatch *partition_dispatch_info;
int num_dispatch;
int num_partitions;
ResultRelInfo *partitions;
TupleConversionMap **partition_tupconv_maps;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@ -1397,6 +1402,71 @@ BeginCopy(ParseState *pstate,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
/*
* Initialize state for CopyFrom tuple routing. Watch out for
* any foreign partitions.
*/
if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDispatch *pd;
List *leaf_parts;
ListCell *cell;
int i,
num_parted,
num_leaf_parts;
ResultRelInfo *leaf_part_rri;
/* Get the tuple-routing information and lock partitions */
pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
&num_parted, &leaf_parts);
num_leaf_parts = list_length(leaf_parts);
cstate->partition_dispatch_info = pd;
cstate->num_dispatch = num_parted;
cstate->num_partitions = num_leaf_parts;
cstate->partitions = (ResultRelInfo *) palloc(num_leaf_parts *
sizeof(ResultRelInfo));
cstate->partition_tupconv_maps = (TupleConversionMap **)
palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
leaf_part_rri = cstate->partitions;
i = 0;
foreach(cell, leaf_parts)
{
Relation partrel;
/*
* We locked all the partitions above including the leaf
* partitions. Note that each of the relations in
* cstate->partitions will be closed by CopyFrom() after
* it's finished with its processing.
*/
partrel = heap_open(lfirst_oid(cell), NoLock);
/*
* Verify result relation is a valid target for the current
* operation.
*/
CheckValidResultRel(partrel, CMD_INSERT);
InitResultRelInfo(leaf_part_rri,
partrel,
1, /* dummy */
false, /* no partition constraint check */
0);
/* Open partition indices */
ExecOpenIndices(leaf_part_rri, false);
if (!equalTupleDescs(tupDesc, RelationGetDescr(partrel)))
cstate->partition_tupconv_maps[i] =
convert_tuples_by_name(tupDesc,
RelationGetDescr(partrel),
gettext_noop("could not convert row type"));
leaf_part_rri++;
i++;
}
}
}
else
{
@ -1751,6 +1821,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from partitioned table \"%s\"",
RelationGetRelationName(rel)),
errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@ -2249,6 +2325,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@ -2275,6 +2352,7 @@ CopyFrom(CopyState cstate)
* only hint about them in the view case.)
*/
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row))
{
@ -2385,6 +2463,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@ -2407,11 +2486,13 @@ CopyFrom(CopyState cstate)
* BEFORE/INSTEAD OF triggers, or we need to evaluate volatile default
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there.
* processed and prepared for insertion are not there. We also can't
* do it if the table is partitioned.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_dispatch_info != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@ -2488,6 +2569,59 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
/* Determine the partition to heap_insert the tuple into */
if (cstate->partition_dispatch_info)
{
int leaf_part_index;
TupleConversionMap *map;
/*
* Away we go ... If we end up not finding a partition after all,
* ExecFindPartition() does not return and errors out instead.
* Otherwise, the returned value is to be used as an index into
* arrays mt_partitions[] and mt_partition_tupconv_maps[] that
* will get us the ResultRelInfo and TupleConversionMap for the
* partition, respectively.
*/
leaf_part_index = ExecFindPartition(resultRelInfo,
cstate->partition_dispatch_info,
slot,
estate);
Assert(leaf_part_index >= 0 &&
leaf_part_index < cstate->num_partitions);
/*
* Save the old ResultRelInfo and switch to the one corresponding
* to the selected partition.
*/
saved_resultRelInfo = resultRelInfo;
resultRelInfo = cstate->partitions + leaf_part_index;
/* We do not yet have a way to insert into a foreign partition */
if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route inserted tuples to a foreign table")));
/*
* For ExecInsertIndexTuples() to work on the partition's indexes
*/
estate->es_result_relation_info = resultRelInfo;
/*
* We might need to convert from the parent rowtype to the
* partition rowtype.
*/
map = cstate->partition_tupconv_maps[leaf_part_index];
if (map)
{
tuple = do_convert_tuple(tuple, map);
ExecStoreTuple(tuple, slot, InvalidBuffer, true);
}
tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
}
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@ -2513,7 +2647,8 @@ CopyFrom(CopyState cstate)
else
{
/* Check the constraints of the tuple */
if (cstate->rel->rd_att->constr)
if (cstate->rel->rd_att->constr ||
resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
@ -2546,7 +2681,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid,
hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot,
@ -2570,6 +2706,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
if (saved_resultRelInfo)
{
resultRelInfo = saved_resultRelInfo;
estate->es_result_relation_info = resultRelInfo;
}
}
}
@ -2607,6 +2749,32 @@ CopyFrom(CopyState cstate)
ExecCloseIndices(resultRelInfo);
/* Close all the partitioned tables, leaf partitions, and their indices */
if (cstate->partition_dispatch_info)
{
int i;
/*
* Remember cstate->partition_dispatch_info[0] corresponds to the root
* partitioned table, which we must not try to close, because it is
* the main target table of COPY that will be closed eventually by
* DoCopy().
*/
for (i = 1; i < cstate->num_dispatch; i++)
{
PartitionDispatch pd = cstate->partition_dispatch_info[i];
heap_close(pd->reldesc, NoLock);
}
for (i = 0; i < cstate->num_partitions; i++)
{
ResultRelInfo *resultRelInfo = cstate->partitions + i;
ExecCloseIndices(resultRelInfo);
heap_close(resultRelInfo->ri_RelationDesc, NoLock);
}
}
FreeExecutorState(estate);
/*

View File

@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
* Create the relation. (This will error out if there's an existing view,
* so we don't need more code to complain if "replace" is false.)
*/
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL);
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
/*
* If necessary, create a TOAST table for the target table. Note that

View File

@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
static Oid GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on partitioned table \"%s\"",
RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Identify the opclass to use.
*/
classOidP[attn] = GetIndexOpClass(attribute->opclass,
atttype,
accessMethodName,
accessMethodId);
classOidP[attn] = ResolveOpClass(attribute->opclass,
atttype,
accessMethodName,
accessMethodId);
/*
* Identify the exclusion operator, if any.
@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*
* Note: This is used to resolve operator class specification in index and
* partition key definitions.
*/
static Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
Oid
ResolveOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;

View File

@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
if (relkind != RELKIND_RELATION)
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",

View File

@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
if (relkind != RELKIND_RELATION)
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
@ -384,7 +384,8 @@ RemovePolicyById(Oid policy_id)
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION)
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",

View File

@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
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("\"%s\" is not a table, view, materialized view, composite type, or foreign table",

View File

@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists;
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL);
address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL);
seqoid = address.objectId;
Assert(seqoid != InvalidOid);
@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("referenced relation \"%s\" is not a table or foreign table",

File diff suppressed because it is too large Load Diff

View File

@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
if (rel->rd_rel->relkind == RELKIND_RELATION)
if (rel->rd_rel->relkind == RELKIND_RELATION ||
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
/* Disallow ROW triggers on partitioned tables */
if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a partitioned table",
RelationGetRelationName(rel)),
errdetail("Partitioned tables cannot have ROW triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid)
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
form->relkind != RELKIND_FOREIGN_TABLE)
form->relkind != RELKIND_FOREIGN_TABLE &&
form->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",

View File

@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
/*
* Finally create the relation. This also creates the type.
*/
DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address);
DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, &address,
NULL);
return address;
}

View File

@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
ereport(WARNING,
(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",

View File

@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL);
address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
NULL);
Assert(address.objectId != InvalidOid);
return address;
}

View File

@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@ -825,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
true,
estate->es_instrument);
resultRelInfo++;
}
@ -1019,6 +1021,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@ -1152,6 +1155,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_SEQUENCE:
@ -1212,6 +1216,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@ -1249,6 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
if (load_partition_check)
resultRelInfo->ri_PartitionCheck =
RelationGetPartitionQual(resultRelationDesc,
true);
}
/*
@ -1311,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@ -1691,6 +1701,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
/*
* ExecPartitionCheck --- check that tuple meets the partition constraint.
*
* Note: This is called *iff* resultRelInfo is the main target table.
*/
static bool
ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
EState *estate)
{
ExprContext *econtext;
/*
* If first time through, build expression state tree for the partition
* check expression. Keep it in the per-query memory context so they'll
* survive throughout the query.
*/
if (resultRelInfo->ri_PartitionCheckExpr == NULL)
{
List *qual = resultRelInfo->ri_PartitionCheck;
resultRelInfo->ri_PartitionCheckExpr = (List *)
ExecPrepareExpr((Expr *) qual, estate);
}
/*
* We will use the EState's per-tuple context for evaluating constraint
* expressions (creating it if it's not already there).
*/
econtext = GetPerTupleExprContext(estate);
/* Arrange for econtext's scan tuple to be the tuple under test */
econtext->ecxt_scantuple = slot;
/*
* As in case of the catalogued constraints, we treat a NULL result as
* success here, not a failure.
*/
return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
}
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@ -1702,9 +1752,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
Assert(constr);
Assert(constr || resultRelInfo->ri_PartitionCheck);
if (constr->has_not_null)
if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@ -1735,7 +1785,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
if (constr->num_check > 0)
if (constr && constr->num_check > 0)
{
const char *failed;
@ -1759,6 +1809,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
if (resultRelInfo->ri_PartitionCheck &&
!ExecPartitionCheck(resultRelInfo, slot, estate))
{
char *val_desc;
insertedCols = GetInsertedColumns(resultRelInfo, estate);
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
tupdesc,
modifiedCols,
64);
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates partition constraint",
RelationGetRelationName(rel)),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
}
}
/*
@ -2926,3 +2996,52 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
/*
* ExecFindPartition -- Find a leaf partition in the partition tree rooted
* at parent, for the heap tuple contained in *slot
*
* estate must be non-NULL; we'll need it to compute any expressions in the
* partition key(s)
*
* If no leaf partition is found, this routine errors out with the appropriate
* error message, else it returns the leaf partition sequence number returned
* by get_partition_for_tuple() unchanged.
*/
int
ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
TupleTableSlot *slot, EState *estate)
{
int result;
Oid failed_at;
ExprContext *econtext = GetPerTupleExprContext(estate);
econtext->ecxt_scantuple = slot;
result = get_partition_for_tuple(pd, slot, estate, &failed_at);
if (result < 0)
{
Relation rel = resultRelInfo->ri_RelationDesc;
char *val_desc;
Bitmapset *insertedCols,
*updatedCols,
*modifiedCols;
TupleDesc tupDesc = RelationGetDescr(rel);
insertedCols = GetInsertedColumns(resultRelInfo, estate);
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
modifiedCols = bms_union(insertedCols, updatedCols);
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
slot,
tupDesc,
modifiedCols,
64);
Assert(OidIsValid(failed_at));
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("no partition of relation \"%s\" found for row",
get_rel_name(failed_at)),
val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
}
return result;
}

View File

@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
/* Determine the partition to heap_insert the tuple into */
if (mtstate->mt_partition_dispatch_info)
{
int leaf_part_index;
TupleConversionMap *map;
/*
* Away we go ... If we end up not finding a partition after all,
* ExecFindPartition() does not return and errors out instead.
* Otherwise, the returned value is to be used as an index into
* arrays mt_partitions[] and mt_partition_tupconv_maps[] that
* will get us the ResultRelInfo and TupleConversionMap for the
* partition, respectively.
*/
leaf_part_index = ExecFindPartition(resultRelInfo,
mtstate->mt_partition_dispatch_info,
slot,
estate);
Assert(leaf_part_index >= 0 &&
leaf_part_index < mtstate->mt_num_partitions);
/*
* Save the old ResultRelInfo and switch to the one corresponding to
* the selected partition.
*/
saved_resultRelInfo = resultRelInfo;
resultRelInfo = mtstate->mt_partitions + leaf_part_index;
/* We do not yet have a way to insert into a foreign partition */
if (resultRelInfo->ri_FdwRoutine)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route inserted tuples to a foreign table")));
/* For ExecInsertIndexTuples() to work on the partition's indexes */
estate->es_result_relation_info = resultRelInfo;
/*
* We might need to convert from the parent rowtype to the partition
* rowtype.
*/
map = mtstate->mt_partition_tupconv_maps[leaf_part_index];
if (map)
{
tuple = do_convert_tuple(tuple, map);
ExecStoreTuple(tuple, slot, InvalidBuffer, true);
}
}
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@ -369,7 +420,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
if (resultRelationDesc->rd_att->constr)
if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
if (saved_resultRelInfo)
{
resultRelInfo = saved_resultRelInfo;
estate->es_result_relation_info = resultRelInfo;
}
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@ -922,7 +979,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
if (resultRelationDesc->rd_att->constr)
if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@ -1655,6 +1713,75 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
/* Build state for INSERT tuple routing */
rel = mtstate->resultRelInfo->ri_RelationDesc;
if (operation == CMD_INSERT &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDispatch *pd;
int i,
j,
num_parted,
num_leaf_parts;
List *leaf_parts;
ListCell *cell;
ResultRelInfo *leaf_part_rri;
/* Form the partition node tree and lock partitions */
pd = RelationGetPartitionDispatchInfo(rel, RowExclusiveLock,
&num_parted, &leaf_parts);
mtstate->mt_partition_dispatch_info = pd;
mtstate->mt_num_dispatch = num_parted;
num_leaf_parts = list_length(leaf_parts);
mtstate->mt_num_partitions = num_leaf_parts;
mtstate->mt_partitions = (ResultRelInfo *)
palloc0(num_leaf_parts * sizeof(ResultRelInfo));
mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
leaf_part_rri = mtstate->mt_partitions;
i = j = 0;
foreach(cell, leaf_parts)
{
Oid partrelid = lfirst_oid(cell);
Relation partrel;
/*
* We locked all the partitions above including the leaf
* partitions. Note that each of the relations in
* mtstate->mt_partitions will be closed by ExecEndModifyTable().
*/
partrel = heap_open(partrelid, NoLock);
/*
* Verify result relation is a valid target for the current
* operation
*/
CheckValidResultRel(partrel, CMD_INSERT);
InitResultRelInfo(leaf_part_rri,
partrel,
1, /* dummy */
false, /* no partition constraint checks */
eflags);
/* Open partition indices (note: ON CONFLICT unsupported)*/
if (partrel->rd_rel->relhasindex && operation != CMD_DELETE &&
leaf_part_rri->ri_IndexRelationDescs == NULL)
ExecOpenIndices(leaf_part_rri, false);
if (!equalTupleDescs(RelationGetDescr(rel),
RelationGetDescr(partrel)))
mtstate->mt_partition_tupconv_maps[i] =
convert_tuples_by_name(RelationGetDescr(rel),
RelationGetDescr(partrel),
gettext_noop("could not convert row type"));
leaf_part_rri++;
i++;
}
}
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@ -1886,7 +2013,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW)
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
@ -1971,6 +2099,26 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo);
}
/* Close all the partitioned tables, leaf partitions, and their indices
*
* Remember node->mt_partition_dispatch_info[0] corresponds to the root
* partitioned table, which we must not try to close, because it is the
* main target table of the query that will be closed by ExecEndPlan().
*/
for (i = 1; i < node->mt_num_dispatch; i++)
{
PartitionDispatch pd = node->mt_partition_dispatch_info[i];
heap_close(pd->reldesc, NoLock);
}
for (i = 0; i < node->mt_num_partitions; i++)
{
ResultRelInfo *resultRelInfo = node->mt_partitions + i;
ExecCloseIndices(resultRelInfo);
heap_close(resultRelInfo->ri_RelationDesc, NoLock);
}
/*
* Free the exprcontext
*/

View File

@ -3030,6 +3030,8 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@ -4188,6 +4190,70 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
static PartitionSpec *
_copyPartitionSpec(const PartitionSpec *from)
{
PartitionSpec *newnode = makeNode(PartitionSpec);
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
COPY_LOCATION_FIELD(location);
return newnode;
}
static PartitionElem *
_copyPartitionElem(const PartitionElem *from)
{
PartitionElem *newnode = makeNode(PartitionElem);
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(expr);
COPY_NODE_FIELD(collation);
COPY_NODE_FIELD(opclass);
COPY_LOCATION_FIELD(location);
return newnode;
}
static PartitionBoundSpec *
_copyPartitionBoundSpec(const PartitionBoundSpec *from)
{
PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
COPY_SCALAR_FIELD(strategy);
COPY_NODE_FIELD(listdatums);
COPY_NODE_FIELD(lowerdatums);
COPY_NODE_FIELD(upperdatums);
COPY_LOCATION_FIELD(location);
return newnode;
}
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
COPY_SCALAR_FIELD(infinite);
COPY_NODE_FIELD(value);
COPY_LOCATION_FIELD(location);
return newnode;
}
static PartitionCmd *
_copyPartitionCmd(const PartitionCmd *from)
{
PartitionCmd *newnode = makeNode(PartitionCmd);
COPY_NODE_FIELD(name);
COPY_NODE_FIELD(bound);
return newnode;
}
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@ -5105,6 +5171,21 @@ copyObject(const void *from)
case T_TriggerTransition:
retval = _copyTriggerTransition(from);
break;
case T_PartitionSpec:
retval = _copyPartitionSpec(from);
break;
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
case T_PartitionCmd:
retval = _copyPartitionCmd(from);
break;
/*
* MISCELLANEOUS NODES

View File

@ -1168,6 +1168,8 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@ -2646,6 +2648,59 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true;
}
static bool
_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
{
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
{
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(expr);
COMPARE_NODE_FIELD(collation);
COMPARE_NODE_FIELD(opclass);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
{
COMPARE_SCALAR_FIELD(strategy);
COMPARE_NODE_FIELD(listdatums);
COMPARE_NODE_FIELD(lowerdatums);
COMPARE_NODE_FIELD(upperdatums);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
COMPARE_SCALAR_FIELD(infinite);
COMPARE_NODE_FIELD(value);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
{
COMPARE_NODE_FIELD(name);
COMPARE_NODE_FIELD(bound);
return true;
}
/*
* Stuff from pg_list.h
*/
@ -3402,6 +3457,21 @@ equal(const void *a, const void *b)
case T_TriggerTransition:
retval = _equalTriggerTransition(a, b);
break;
case T_PartitionSpec:
retval = _equalPartitionSpec(a, b);
break;
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
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",

View File

@ -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;

View File

@ -2392,6 +2392,8 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@ -3277,6 +3279,47 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
static void
_outPartitionSpec(StringInfo str, const PartitionSpec *node)
{
WRITE_NODE_TYPE("PARTITIONBY");
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
WRITE_LOCATION_FIELD(location);
}
static void
_outPartitionElem(StringInfo str, const PartitionElem *node)
{
WRITE_NODE_TYPE("PARTITIONELEM");
WRITE_STRING_FIELD(name);
WRITE_NODE_FIELD(expr);
WRITE_NODE_FIELD(collation);
WRITE_NODE_FIELD(opclass);
WRITE_LOCATION_FIELD(location);
}
static void
_outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
{
WRITE_NODE_TYPE("PARTITIONBOUND");
WRITE_CHAR_FIELD(strategy);
WRITE_NODE_FIELD(listdatums);
WRITE_NODE_FIELD(lowerdatums);
WRITE_NODE_FIELD(upperdatums);
}
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
WRITE_NODE_TYPE("PARTRANGEDATUM");
WRITE_BOOL_FIELD(infinite);
WRITE_NODE_FIELD(value);
}
/*
* outNode -
@ -3865,6 +3908,18 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition:
_outTriggerTransition(str, obj);
break;
case T_PartitionSpec:
_outPartitionSpec(str, obj);
break;
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
default:

View File

@ -2265,6 +2265,36 @@ _readExtensibleNode(void)
READ_DONE();
}
/*
* _readPartitionBoundSpec
*/
static PartitionBoundSpec *
_readPartitionBoundSpec(void)
{
READ_LOCALS(PartitionBoundSpec);
READ_CHAR_FIELD(strategy);
READ_NODE_FIELD(listdatums);
READ_NODE_FIELD(lowerdatums);
READ_NODE_FIELD(upperdatums);
READ_DONE();
}
/*
* _readPartitionRangeDatum
*/
static PartitionRangeDatum *
_readPartitionRangeDatum(void)
{
READ_LOCALS(PartitionRangeDatum);
READ_BOOL_FIELD(infinite);
READ_NODE_FIELD(value);
READ_DONE();
}
/*
* parseNodeString
*
@ -2497,6 +2527,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
else if (MATCH("PARTITIONBOUND", 14))
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTRANGEDATUM", 14))
return_value = _readPartitionRangeDatum();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);

View File

@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@ -1140,6 +1141,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
List *pcqual;
/*
* We assume the relation has already been safely locked.
@ -1225,6 +1227,24 @@ get_relation_constraints(PlannerInfo *root,
}
}
/* Append partition predicates, if any */
pcqual = RelationGetPartitionQual(relation, true);
if (pcqual)
{
/*
* Run each expression through const-simplification and
* canonicalization similar to check constraints.
*/
pcqual = (List *) eval_const_expressions(root, (Node *) pcqual);
pcqual = (List *) canonicalize_qual((Expr *) pcqual);
/* Fix Vars to have the desired varno */
if (varno != 1)
ChangeVarNodes((Node *) pcqual, 1, varno, 0);
result = list_concat(result, pcqual);
}
heap_close(relation, NoLock);
return result;

View File

@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
{
/* Bail out if target relation is partitioned table */
if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ON CONFLICT clause is not supported with partitioned tables")));
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
}
/*
* If we have a RETURNING clause, we need to add the target relation to

View File

@ -229,6 +229,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionRangeDatum *partrange_datum;
}
%type <node> stmt schema_stmt
@ -276,7 +279,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> add_drop opt_asc_desc opt_nulls_order
%type <node> alter_table_cmd alter_type_cmd opt_collate_clause
replica_identity
replica_identity partition_cmd
%type <list> alter_table_cmds alter_type_cmds
%type <dbehavior> opt_drop_behavior
@ -545,6 +548,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
%type <partspec> PartitionSpec OptPartitionSpec
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
%type <list> OptPartitionElementList PartitionElementList
%type <node> PartitionElement
%type <node> ForValues
%type <node> partbound_datum
%type <list> partbound_datum_list
%type <partrange_datum> PartitionRangeDatum
%type <list> range_datum_list
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@ -570,7 +584,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@ -586,7 +600,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@ -1787,6 +1802,24 @@ AlterTableStmt:
n->missing_ok = true;
$$ = (Node *)n;
}
| ALTER TABLE relation_expr partition_cmd
{
AlterTableStmt *n = makeNode(AlterTableStmt);
n->relation = $3;
n->cmds = list_make1($4);
n->relkind = OBJECT_TABLE;
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER TABLE IF_P EXISTS relation_expr partition_cmd
{
AlterTableStmt *n = makeNode(AlterTableStmt);
n->relation = $5;
n->cmds = list_make1($6);
n->relkind = OBJECT_TABLE;
n->missing_ok = true;
$$ = (Node *)n;
}
| ALTER TABLE ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait
{
AlterTableMoveAllStmt *n =
@ -1932,6 +1965,34 @@ alter_table_cmds:
| alter_table_cmds ',' alter_table_cmd { $$ = lappend($1, $3); }
;
partition_cmd:
/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
ATTACH PARTITION qualified_name ForValues
{
AlterTableCmd *n = makeNode(AlterTableCmd);
PartitionCmd *cmd = makeNode(PartitionCmd);
n->subtype = AT_AttachPartition;
cmd->name = $3;
cmd->bound = (Node *) $4;
n->def = (Node *) cmd;
$$ = (Node *) n;
}
/* ALTER TABLE <name> DETACH PARTITION <partition_name> */
| DETACH PARTITION qualified_name
{
AlterTableCmd *n = makeNode(AlterTableCmd);
PartitionCmd *cmd = makeNode(PartitionCmd);
n->subtype = AT_DetachPartition;
cmd->name = $3;
n->def = (Node *) cmd;
$$ = (Node *) n;
}
;
alter_table_cmd:
/* ALTER TABLE <name> ADD <coldef> */
ADD_P columnDef
@ -2467,6 +2528,73 @@ reloption_elem:
}
;
ForValues:
/* a LIST partition */
FOR VALUES IN_P '(' partbound_datum_list ')'
{
PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
n->strategy = PARTITION_STRATEGY_LIST;
n->listdatums = $5;
n->location = @3;
$$ = (Node *) n;
}
/* a RANGE partition */
| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
{
PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
n->strategy = PARTITION_STRATEGY_RANGE;
n->lowerdatums = $5;
n->upperdatums = $9;
n->location = @3;
$$ = (Node *) n;
}
;
partbound_datum:
Sconst { $$ = makeStringConst($1, @1); }
| NumericOnly { $$ = makeAConst($1, @1); }
| NULL_P { $$ = makeNullAConst(@1); }
;
partbound_datum_list:
partbound_datum { $$ = list_make1($1); }
| partbound_datum_list ',' partbound_datum
{ $$ = lappend($1, $3); }
;
range_datum_list:
PartitionRangeDatum { $$ = list_make1($1); }
| range_datum_list ',' PartitionRangeDatum
{ $$ = lappend($1, $3); }
;
PartitionRangeDatum:
UNBOUNDED
{
PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
n->infinite = true;
n->value = NULL;
n->location = @1;
$$ = n;
}
| partbound_datum
{
PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
n->infinite = false;
n->value = $1;
n->location = @1;
$$ = n;
}
;
/*****************************************************************************
*
@ -2812,31 +2940,71 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
OptInherit OptWith OnCommitOption OptTableSpace
OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
n->partspec = $9;
n->ofTypename = NULL;
n->constraints = NIL;
n->options = $10;
n->oncommit = $11;
n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptPartitionSpec OptWith
OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
n->partspec = $12;
n->ofTypename = NULL;
n->constraints = NIL;
n->options = $13;
n->oncommit = $14;
n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
n->partspec = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
n->options = $9;
n->oncommit = $10;
n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
OptTableElementList ')' OptInherit OptWith OnCommitOption
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption
OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
n->ofTypename = NULL;
n->tableElts = $10;
n->inhRelations = NIL;
n->partspec = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
n->options = $12;
n->oncommit = $13;
@ -2844,37 +3012,41 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
OptTypedTableElementList OptWith OnCommitOption OptTableSpace
| 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 = $7;
n->inhRelations = NIL;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
n->options = $8;
n->oncommit = $9;
n->tablespacename = $10;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
OptTypedTableElementList OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
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;
}
@ -2923,6 +3095,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
OptPartitionElementList:
'(' PartitionElementList ')' { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
;
TableElementList:
TableElement
{
@ -2945,6 +3122,17 @@ TypedTableElementList:
}
;
PartitionElementList:
PartitionElement
{
$$ = list_make1($1);
}
| PartitionElementList ',' PartitionElement
{
$$ = lappend($1, $3);
}
;
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@ -2956,6 +3144,28 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
PartitionElement:
TableConstraint { $$ = $1; }
| ColId ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
n->colname = $1;
n->typeName = NULL;
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
n->collOid = InvalidOid;
SplitColQualList($2, &n->constraints, &n->collClause,
yyscanner);
n->location = @1;
$$ = (Node *) n;
}
;
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@ -3419,6 +3629,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
/* Optional partition key specification */
OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
PartitionSpec: PARTITION BY part_strategy '(' part_params ')'
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
$$ = n;
}
;
part_strategy: IDENT { $$ = $1; }
| unreserved_keyword { $$ = pstrdup($1); }
;
part_params: part_elem { $$ = list_make1($1); }
| part_params ',' part_elem { $$ = lappend($1, $3); }
;
part_elem: ColId opt_collate opt_class
{
PartitionElem *n = makeNode(PartitionElem);
n->name = $1;
n->expr = NULL;
n->collation = $2;
n->opclass = $3;
n->location = @1;
$$ = n;
}
| func_expr_windowless opt_collate opt_class
{
PartitionElem *n = makeNode(PartitionElem);
n->name = NULL;
n->expr = $1;
n->collation = $2;
n->opclass = $3;
n->location = @1;
$$ = n;
}
| '(' a_expr ')' opt_collate opt_class
{
PartitionElem *n = makeNode(PartitionElem);
n->name = NULL;
n->expr = $2;
n->collation = $4;
n->opclass = $5;
n->location = @1;
$$ = n;
}
;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@ -4484,6 +4753,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;
}
;
/*****************************************************************************
@ -13703,6 +14014,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
| ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@ -13749,6 +14061,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
| DETACH
| DICTIONARY
| DISABLE_P
| DISCARD

View File

@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_EXPRESSION:
if (isAgg)
err = _("aggregate functions are not allowed in partition key expression");
else
err = _("grouping operations are not allowed in partition key expression");
break;
/*
* There is intentionally no default: case here, so that the
@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_EXPRESSION:
err = _("window functions are not allowed in partition key expression");
break;
/*
* There is intentionally no default: case here, so that the

View File

@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_EXPRESSION:
err = _("cannot use subquery in partition key expression");
break;
/*
* There is intentionally no default: case here, so that the
@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
case EXPR_KIND_PARTITION_EXPRESSION:
return "PARTITION BY";
/*
* There is intentionally no default: case here, so that the

View File

@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_EXPRESSION:
err = _("set-returning functions are not allowed in partition key expression");
break;
/*
* There is intentionally no default: case here, so that the

View File

@ -47,8 +47,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@ -62,6 +64,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@ -87,6 +90,8 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@ -129,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
/*
@ -229,6 +235,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@ -247,6 +254,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
if (stmt->partspec)
{
int partnatts = list_length(stmt->partspec->partParams);
if (stmt->inhRelations && !stmt->partbound)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot create partitioned table as inheritance child")));
if (partnatts > PARTITION_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("cannot partition using more than %d columns",
PARTITION_MAX_KEYS)));
if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
partnatts > 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot list partition using more than one column")));
}
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@ -583,6 +612,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("primary key constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@ -592,6 +627,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unique constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@ -609,6 +650,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign key constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@ -674,6 +721,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("primary key constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@ -684,6 +737,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unique constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@ -694,6 +753,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("exclusion constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@ -708,6 +773,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign key constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@ -763,7 +834,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
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("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
@ -1854,7 +1926,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
rel = heap_openrv(inh, AccessShareLock);
/* check user requested inheritance from valid relkind */
if (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)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@ -2512,6 +2585,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
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
@ -2594,6 +2669,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
case AT_AttachPartition:
{
PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
transformAttachPartition(&cxt, partcmd);
/* assign transformed values */
partcmd->bound = cxt.partbound;
}
newcmds = lappend(newcmds, cmd);
break;
default:
newcmds = lappend(newcmds, cmd);
break;
@ -2958,3 +3046,237 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
/*
* transformAttachPartition
* Analyze ATTACH PARTITION ... FOR VALUES ...
*/
static void
transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
{
Relation parentRel = cxt->rel;
/*
* We are going to try to validate the partition bound specification
* against the partition key of rel, so it better have one.
*/
if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("\"%s\" is not partitioned",
RelationGetRelationName(parentRel))));
/* tranform the values */
Assert(RelationGetPartitionKey(parentRel) != NULL);
cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
cmd->bound);
}
/*
* transformPartitionBound
*
* Transform partition bound specification
*/
Node *
transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
{
PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
*result_spec;
PartitionKey key = RelationGetPartitionKey(parent);
char strategy = get_partition_strategy(key);
int partnatts = get_partition_natts(key);
List *partexprs = get_partition_exprs(key);
result_spec = copyObject(spec);
if (strategy == PARTITION_STRATEGY_LIST)
{
ListCell *cell;
char *colname;
/* Get the only column's name in case we need to output an error */
if (key->partattrs[0] != 0)
colname = get_relid_attribute_name(RelationGetRelid(parent),
key->partattrs[0]);
else
colname = deparse_expression((Node *) linitial(partexprs),
deparse_context_for(RelationGetRelationName(parent),
RelationGetRelid(parent)),
false, false);
if (spec->strategy != PARTITION_STRATEGY_LIST)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid bound specification for a list partition"),
parser_errposition(pstate, exprLocation(bound))));
result_spec->listdatums = NIL;
foreach(cell, spec->listdatums)
{
A_Const *con = (A_Const *) lfirst(cell);
Node *value;
ListCell *cell2;
bool duplicate;
value = (Node *) make_const(pstate, &con->val, con->location);
value = coerce_to_target_type(pstate,
value, exprType(value),
get_partition_col_typid(key, 0),
get_partition_col_typmod(key, 0),
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (value == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
format_type_be(get_partition_col_typid(key, 0)),
colname),
parser_errposition(pstate,
exprLocation((Node *) con))));
/* Simplify the expression */
value = (Node *) expression_planner((Expr *) value);
/* Don't add to the result if the value is a duplicate */
duplicate = false;
foreach(cell2, result_spec->listdatums)
{
Const *value2 = (Const *) lfirst(cell2);
if (equal(value, value2))
{
duplicate = true;
break;
}
}
if (duplicate)
continue;
result_spec->listdatums = lappend(result_spec->listdatums,
value);
}
}
else if (strategy == PARTITION_STRATEGY_RANGE)
{
ListCell *cell1,
*cell2;
int i,
j;
char *colname;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("invalid bound specification for a range partition"),
parser_errposition(pstate, exprLocation(bound))));
Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
if (list_length(spec->lowerdatums) != partnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("FROM must specify exactly one value per partitioning column")));
if (list_length(spec->upperdatums) != partnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("TO must specify exactly one value per partitioning column")));
i = j = 0;
result_spec->lowerdatums = result_spec->upperdatums = NIL;
forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
{
PartitionRangeDatum *ldatum,
*rdatum;
Node *value;
A_Const *lcon = NULL,
*rcon = NULL;
ldatum = (PartitionRangeDatum *) lfirst(cell1);
rdatum = (PartitionRangeDatum *) lfirst(cell2);
/* Get the column's name in case we need to output an error */
if (key->partattrs[i] != 0)
colname = get_relid_attribute_name(RelationGetRelid(parent),
key->partattrs[i]);
else
{
colname = deparse_expression((Node *) list_nth(partexprs, j),
deparse_context_for(RelationGetRelationName(parent),
RelationGetRelid(parent)),
false, false);
++j;
}
if (!ldatum->infinite)
lcon = (A_Const *) ldatum->value;
if (!rdatum->infinite)
rcon = (A_Const *) rdatum->value;
if (lcon)
{
value = (Node *) make_const(pstate, &lcon->val, lcon->location);
if (((Const *) value)->constisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot specify NULL in range bound")));
value = coerce_to_target_type(pstate,
value, exprType(value),
get_partition_col_typid(key, i),
get_partition_col_typmod(key, i),
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (value == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
format_type_be(get_partition_col_typid(key, i)),
colname),
parser_errposition(pstate, exprLocation((Node *) ldatum))));
/* Simplify the expression */
value = (Node *) expression_planner((Expr *) value);
ldatum->value = value;
}
if (rcon)
{
value = (Node *) make_const(pstate, &rcon->val, rcon->location);
if (((Const *) value)->constisnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot specify NULL in range bound")));
value = coerce_to_target_type(pstate,
value, exprType(value),
get_partition_col_typid(key, i),
get_partition_col_typmod(key, i),
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
if (value == NULL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
format_type_be(get_partition_col_typid(key, i)),
colname),
parser_errposition(pstate, exprLocation((Node *) rdatum))));
/* Simplify the expression */
value = (Node *) expression_planner((Expr *) value);
rdatum->value = value;
}
result_spec->lowerdatums = lappend(result_spec->lowerdatums,
copyObject(ldatum));
result_spec->upperdatums = lappend(result_spec->upperdatums,
copyObject(rdatum));
++i;
}
}
else
elog(ERROR, "unexpected partition strategy: %d", (int) strategy);
return (Node *) result_spec;
}

View File

@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename,
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
event_relation->rd_rel->relkind != RELKIND_VIEW &&
event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view",

View File

@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Emit CTID so that executor can find the row to update or delete.

View File

@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
*hasSubLinks = false;
/* If this is not a normal relation, just return immediately */
if (rte->relkind != RELKIND_RELATION)
if (rte->relkind != RELKIND_RELATION &&
rte->relkind != RELKIND_PARTITIONED_TABLE)
return;
/* Switch to checkAsUser if it's set */

View File

@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_RELATION,
InvalidOid, NULL);
InvalidOid, NULL,
queryString);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
RELKIND_FOREIGN_TABLE,
InvalidOid, NULL);
InvalidOid, NULL,
queryString);
CreateForeignTable((CreateForeignTableStmt *) stmt,
address.objectId);
EventTriggerCollectSimpleCommand(address,

View File

@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags, bool missing_ok);
static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags, bool missing_ok);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@ -1415,6 +1417,163 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
/*
* pg_get_partkeydef
*
* Returns the partition key specification, ie, the following:
*
* PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...])
*/
Datum
pg_get_partkeydef(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
PRETTYFLAG_INDENT)));
}
/*
* Internal workhorse to decompile a partition key definition.
*/
static char *
pg_get_partkeydef_worker(Oid relid, int prettyFlags)
{
Form_pg_partitioned_table form;
HeapTuple tuple;
oidvector *partclass;
oidvector *partcollation;
List *partexprs;
ListCell *partexpr_item;
List *context;
Datum datum;
bool isnull;
StringInfoData buf;
int keyno;
char *str;
char *sep;
tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for partition key of %u", relid);
form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
Assert(form->partrelid == relid);
/* Must get partclass and partcollation the hard way */
datum = SysCacheGetAttr(PARTRELID, tuple,
Anum_pg_partitioned_table_partclass, &isnull);
Assert(!isnull);
partclass = (oidvector *) DatumGetPointer(datum);
datum = SysCacheGetAttr(PARTRELID, tuple,
Anum_pg_partitioned_table_partcollation, &isnull);
Assert(!isnull);
partcollation = (oidvector *) DatumGetPointer(datum);
/*
* Get the expressions, if any. (NOTE: we do not use the relcache
* versions of the expressions, because we want to display non-const-folded
* expressions.)
*/
if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
{
Datum exprsDatum;
bool isnull;
char *exprsString;
exprsDatum = SysCacheGetAttr(PARTRELID, tuple,
Anum_pg_partitioned_table_partexprs, &isnull);
Assert(!isnull);
exprsString = TextDatumGetCString(exprsDatum);
partexprs = (List *) stringToNode(exprsString);
if (!IsA(partexprs, List))
elog(ERROR, "unexpected node type found in partexprs: %d",
(int) nodeTag(partexprs));
pfree(exprsString);
}
else
partexprs = NIL;
partexpr_item = list_head(partexprs);
context = deparse_context_for(get_relation_name(relid), relid);
initStringInfo(&buf);
switch (form->partstrat)
{
case PARTITION_STRATEGY_LIST:
appendStringInfo(&buf, "LIST");
break;
case PARTITION_STRATEGY_RANGE:
appendStringInfo(&buf, "RANGE");
break;
default:
elog(ERROR, "unexpected partition strategy: %d",
(int) form->partstrat);
}
appendStringInfo(&buf, " (");
sep = "";
for (keyno = 0; keyno < form->partnatts; keyno++)
{
AttrNumber attnum = form->partattrs.values[keyno];
Oid keycoltype;
Oid keycolcollation;
Oid partcoll;
appendStringInfoString(&buf, sep);
sep = ", ";
if (attnum != 0)
{
/* Simple attribute reference */
char *attname;
int32 keycoltypmod;
attname = get_relid_attribute_name(relid, attnum);
appendStringInfoString(&buf, quote_identifier(attname));
get_atttypetypmodcoll(relid, attnum,
&keycoltype, &keycoltypmod,
&keycolcollation);
}
else
{
/* Expression */
Node *partkey;
if (partexpr_item == NULL)
elog(ERROR, "too few entries in partexprs list");
partkey = (Node *) lfirst(partexpr_item);
partexpr_item = lnext(partexpr_item);
/* Deparse */
str = deparse_expression_pretty(partkey, context, false, false,
0, 0);
appendStringInfoString(&buf, str);
keycoltype = exprType(partkey);
keycolcollation = exprCollation(partkey);
}
/* Add collation, if not default for column */
partcoll = partcollation->values[keyno];
if (OidIsValid(partcoll) && partcoll != keycolcollation)
appendStringInfo(&buf, " COLLATE %s",
generate_collation_name((partcoll)));
/* Add the operator class name, if not default */
get_opclass_name(partclass->values[keyno], keycoltype, &buf);
}
appendStringInfoChar(&buf, ')');
/* Clean up */
ReleaseSysCache(tuple);
return buf.data;
}
/*
* pg_get_constraintdef
@ -8291,6 +8450,88 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
case T_PartitionBoundSpec:
{
PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
ListCell *cell;
char *sep;
switch (spec->strategy)
{
case PARTITION_STRATEGY_LIST:
Assert(spec->listdatums != NIL);
appendStringInfoString(buf, "FOR VALUES");
appendStringInfoString(buf, " IN (");
sep = "";
foreach (cell, spec->listdatums)
{
Const *val = lfirst(cell);
appendStringInfoString(buf, sep);
get_const_expr(val, context, -1);
sep = ", ";
}
appendStringInfoString(buf, ")");
break;
case PARTITION_STRATEGY_RANGE:
Assert(spec->lowerdatums != NIL &&
spec->upperdatums != NIL &&
list_length(spec->lowerdatums) ==
list_length(spec->upperdatums));
appendStringInfoString(buf, "FOR VALUES");
appendStringInfoString(buf, " FROM");
appendStringInfoString(buf, " (");
sep = "";
foreach (cell, spec->lowerdatums)
{
PartitionRangeDatum *datum = lfirst(cell);
Const *val;
appendStringInfoString(buf, sep);
if (datum->infinite)
appendStringInfoString(buf, "UNBOUNDED");
else
{
val = (Const *) datum->value;
get_const_expr(val, context, -1);
}
sep = ", ";
}
appendStringInfoString(buf, ")");
appendStringInfoString(buf, " TO");
appendStringInfoString(buf, " (");
sep = "";
foreach (cell, spec->upperdatums)
{
PartitionRangeDatum *datum = lfirst(cell);
Const *val;
appendStringInfoString(buf, sep);
if (datum->infinite)
appendStringInfoString(buf, "UNBOUNDED");
else
{
val = (Const *) datum->value;
get_const_expr(val, context, -1);
}
sep = ", ";
}
appendStringInfoString(buf, ")");
break;
default:
elog(ERROR, "unrecognized partition strategy: %d",
(int) spec->strategy);
break;
}
}
break;
case T_List:
{
char *sep;

View File

@ -32,6 +32,7 @@
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/nbtree.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
@ -40,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"
@ -49,6 +51,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@ -258,6 +261,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi
static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
static void RelationBuildPartitionKey(Relation relation);
static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@ -278,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 partdesc1,
PartitionDesc partdesc2);
/*
@ -435,6 +442,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
case RELKIND_INDEX:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
break;
default:
return;
@ -795,6 +803,237 @@ RelationBuildRuleLock(Relation relation)
relation->rd_rules = rulelock;
}
/*
* RelationBuildPartitionKey
* Build and attach to relcache partition key data of relation
*
* Partitioning key data is stored in CacheMemoryContext to ensure it survives
* as long as the relcache. To avoid leaking memory in that context in case
* of an error partway through this function, we build the structure in the
* working context (which must be short-lived) and copy the completed
* structure into the cache memory.
*
* Also, since the structure being created here is sufficiently complex, we
* make a private child context of CacheMemoryContext for each relation that
* has associated partition key information. That means no complicated logic
* to free individual elements whenever the relcache entry is flushed - just
* delete the context.
*/
static void
RelationBuildPartitionKey(Relation relation)
{
Form_pg_partitioned_table form;
HeapTuple tuple;
bool isnull;
int i;
PartitionKey key;
AttrNumber *attrs;
oidvector *opclass;
oidvector *collation;
ListCell *partexprs_item;
Datum datum;
MemoryContext partkeycxt,
oldcxt;
tuple = SearchSysCache1(PARTRELID,
ObjectIdGetDatum(RelationGetRelid(relation)));
/*
* The following happens when we have created our pg_class entry but not
* the pg_partitioned_table entry yet.
*/
if (!HeapTupleIsValid(tuple))
return;
key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
/* Fixed-length attributes */
form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
key->strategy = form->partstrat;
key->partnatts = form->partnatts;
/*
* We can rely on the first variable-length attribute being mapped to the
* relevant field of the catalog's C struct, because all previous
* attributes are non-nullable and fixed-length.
*/
attrs = form->partattrs.values;
/* But use the hard way to retrieve further variable-length attributes */
/* Operator class */
datum = SysCacheGetAttr(PARTRELID, tuple,
Anum_pg_partitioned_table_partclass, &isnull);
Assert(!isnull);
opclass = (oidvector *) DatumGetPointer(datum);
/* Collation */
datum = SysCacheGetAttr(PARTRELID, tuple,
Anum_pg_partitioned_table_partcollation, &isnull);
Assert(!isnull);
collation = (oidvector *) DatumGetPointer(datum);
/* Expressions */
datum = SysCacheGetAttr(PARTRELID, tuple,
Anum_pg_partitioned_table_partexprs, &isnull);
if (!isnull)
{
char *exprString;
Node *expr;
exprString = TextDatumGetCString(datum);
expr = stringToNode(exprString);
pfree(exprString);
/*
* Run the expressions through const-simplification since the planner
* will be comparing them to similarly-processed qual clause operands,
* and may fail to detect valid matches without this step. We don't
* need to bother with canonicalize_qual() though, because partition
* expressions are not full-fledged qualification clauses.
*/
expr = eval_const_expressions(NULL, (Node *) expr);
/* May as well fix opfuncids too */
fix_opfuncids((Node *) expr);
key->partexprs = (List *) expr;
}
key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
/* Gather type and collation info as well */
key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char));
key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
/* Copy partattrs and fill other per-attribute info */
memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
partexprs_item = list_head(key->partexprs);
for (i = 0; i < key->partnatts; i++)
{
AttrNumber attno = key->partattrs[i];
HeapTuple opclasstup;
Form_pg_opclass opclassform;
Oid funcid;
/* Collect opfamily information */
opclasstup = SearchSysCache1(CLAOID,
ObjectIdGetDatum(opclass->values[i]));
if (!HeapTupleIsValid(opclasstup))
elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
key->partopfamily[i] = opclassform->opcfamily;
key->partopcintype[i] = opclassform->opcintype;
/*
* A btree support function covers the cases of list and range methods
* currently supported.
*/
funcid = get_opfamily_proc(opclassform->opcfamily,
opclassform->opcintype,
opclassform->opcintype,
BTORDER_PROC);
fmgr_info(funcid, &key->partsupfunc[i]);
/* Collation */
key->partcollation[i] = collation->values[i];
/* Collect type information */
if (attno != 0)
{
key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
}
else
{
key->parttypid[i] = exprType(lfirst(partexprs_item));
key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
}
get_typlenbyvalalign(key->parttypid[i],
&key->parttyplen[i],
&key->parttypbyval[i],
&key->parttypalign[i]);
ReleaseSysCache(opclasstup);
}
ReleaseSysCache(tuple);
/* Success --- now copy to the cache memory */
partkeycxt = AllocSetContextCreate(CacheMemoryContext,
RelationGetRelationName(relation),
ALLOCSET_SMALL_SIZES);
relation->rd_partkeycxt = partkeycxt;
oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
relation->rd_partkey = copy_partition_key(key);
MemoryContextSwitchTo(oldcxt);
}
/*
* copy_partition_key
*
* The copy is allocated in the current memory context.
*/
static PartitionKey
copy_partition_key(PartitionKey fromkey)
{
PartitionKey newkey;
int n;
newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
newkey->strategy = fromkey->strategy;
newkey->partnatts = n = fromkey->partnatts;
newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
newkey->partexprs = copyObject(fromkey->partexprs);
newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
newkey->parttypalign = (char *) palloc(n * sizeof(bool));
memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
return newkey;
}
/*
* equalRuleLocks
*
@ -922,6 +1161,58 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
return true;
}
/*
* equalPartitionDescs
* Compare two partition descriptors for logical equality
*/
static bool
equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
PartitionDesc partdesc2)
{
int i;
if (partdesc1 != NULL)
{
if (partdesc2 == NULL)
return false;
if (partdesc1->nparts != partdesc2->nparts)
return false;
Assert(key != NULL || partdesc1->nparts == 0);
/*
* Same oids? If the partitioning structure did not change, that is,
* no partitions were added or removed to the relation, the oids array
* should still match element-by-element.
*/
for (i = 0; i < partdesc1->nparts; i++)
{
if (partdesc1->oids[i] != partdesc2->oids[i])
return false;
}
/*
* Now compare partition bound collections. The logic to iterate over
* the collections is private to partition.c.
*/
if (partdesc1->boundinfo != NULL)
{
if (partdesc2->boundinfo == NULL)
return false;
if (!partition_bounds_equal(key, partdesc1->boundinfo,
partdesc2->boundinfo))
return false;
}
else if (partdesc2->boundinfo != NULL)
return false;
}
else if (partdesc2 != NULL)
return false;
return true;
}
/*
* RelationBuildDesc
*
@ -1050,6 +1341,20 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
/* 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;
}
/*
* if it's an index, initialize index-related information
*/
@ -2042,6 +2347,12 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
if (relation->rd_partkeycxt)
MemoryContextDelete(relation->rd_partkeycxt);
if (relation->rd_pdcxt)
MemoryContextDelete(relation->rd_pdcxt);
if (relation->rd_partcheck)
pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@ -2190,11 +2501,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, partition key, 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
@ -2205,6 +2517,8 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
bool keep_partkey;
bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@ -2235,6 +2549,10 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
keep_partkey = (relation->rd_partkey != NULL);
keep_partdesc = equalPartitionDescs(relation->rd_partkey,
relation->rd_partdesc,
newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@ -2289,6 +2607,18 @@ RelationClearRelation(Relation relation, bool rebuild)
SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
/* partition key must be preserved, if we have one */
if (keep_partkey)
{
SWAPFIELD(PartitionKey, rd_partkey);
SWAPFIELD(MemoryContext, rd_partkeycxt);
}
/* preserve old partdesc if no logical change */
if (keep_partdesc)
{
SWAPFIELD(PartitionDesc, rd_partdesc);
SWAPFIELD(MemoryContext, rd_pdcxt);
}
#undef SWAPFIELD
@ -2983,7 +3313,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
(relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@ -3514,6 +3846,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);
@ -4267,6 +4613,8 @@ RelationGetIndexExpressions(Relation relation)
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
result = (List *) canonicalize_qual((Expr *) result);
/* May as well fix opfuncids too */
fix_opfuncids((Node *) result);
@ -5035,6 +5383,10 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkeycxt = NULL;
rel->rd_partkey = NULL;
rel->rd_partdesc = NULL;
rel->rd_partcheck = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;

View File

@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
{PartitionedRelationId, /* PARTRELID */
PartitionedRelidIndexId,
1,
{
Anum_pg_partitioned_table_partrelid,
0,
0,
0
},
32
},
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,

View File

@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
static void flagPartitions(TableInfo *tblinfo, int numTables,
PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
PartInfo *partinfo;
int numAggregates;
int numInherits;
int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@ -231,6 +237,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading table inheritance information\n");
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
write_msg(NULL, "reading partition information\n");
partinfo = getPartitions(fout, &numPartitions);
if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
/* Link tables to partition parents, mark parents as interesting */
if (g_verbose)
write_msg(NULL, "finding partition relationships\n");
flagPartitions(tblinfo, numTables, partinfo, numPartitions);
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@ -273,6 +288,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading policies\n");
getPolicies(fout, tblinfo, numTables);
if (g_verbose)
write_msg(NULL, "reading partition key information for interesting tables\n");
getTablePartitionKeyInfo(fout, tblinfo, numTables);
*numTablesPtr = numTables;
return tblinfo;
}
@ -319,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
/* flagPartitions -
* Fill in parent link fields of every target table that is partition,
* and mark parents of partitions as interesting
*
* modifies tblinfo
*/
static void
flagPartitions(TableInfo *tblinfo, int numTables,
PartInfo *partinfo, int numPartitions)
{
int i;
for (i = 0; i < numTables; i++)
{
/* Some kinds are never partitions */
if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
tblinfo[i].relkind == RELKIND_VIEW ||
tblinfo[i].relkind == RELKIND_MATVIEW)
continue;
/* Don't bother computing anything for non-target tables, either */
if (!tblinfo[i].dobj.dump)
continue;
/* Find the parent TableInfo and save */
findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
/* Mark the parent as interesting for getTableAttrs */
if (tblinfo[i].partitionOf)
{
tblinfo[i].partitionOf->interesting = true;
addObjectDependency(&tblinfo[i].dobj,
tblinfo[i].partitionOf->dobj.dumpId);
}
}
}
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@ -919,6 +975,40 @@ findParentsByOid(TableInfo *self,
self->parents = NULL;
}
/*
* findPartitionParentByOid
* find a partition's parent in tblinfo[]
*/
static void
findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
int numPartitions)
{
Oid oid = self->dobj.catId.oid;
int i;
for (i = 0; i < numPartitions; i++)
{
if (partinfo[i].partrelid == oid)
{
TableInfo *parent;
parent = findTableByOid(partinfo[i].partparent);
if (parent == NULL)
{
write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
partinfo[i].partparent,
self->dobj.name,
oid);
exit_nicely(1);
}
self->partitionOf = parent;
/* While we're at it, also save the partdef */
self->partitiondef = partinfo[i].partdef;
}
}
}
/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array

View File

@ -1239,9 +1239,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
"\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
"\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@ -2098,6 +2099,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
/* Skip partitioned tables (data in partitions) */
if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@ -4993,7 +4997,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid "
"AND pip.classoid = 'pg_class'::regclass "
"AND pip.objsubid = 0) "
"WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
"WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@ -5007,7 +5011,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@ -5535,7 +5540,9 @@ getTables(Archive *fout, int *numTables)
* We only need to lock the table for certain components; see
* pg_dump.h
*/
if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
if (tblinfo[i].dobj.dump &&
(tblinfo[i].relkind == RELKIND_RELATION ||
tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@ -5635,9 +5642,16 @@ getInherits(Archive *fout, int *numInherits)
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
/* find all the inheritance information */
appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
/*
* Find all the inheritance information, excluding implicit inheritance
* via partitioning. We handle that case using getPartitions(), because
* we want more information about partitions than just the parent-child
* relationship.
*/
appendPQExpBufferStr(query,
"SELECT inhrelid, inhparent "
"FROM pg_inherits "
"WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@ -5663,6 +5677,70 @@ getInherits(Archive *fout, int *numInherits)
return inhinfo;
}
/*
* getPartitions
* read all the partition inheritance and partition bound information
* from the system catalogs return them in the PartInfo* structure
*
* numPartitions is set to the number of pairs read in
*/
PartInfo *
getPartitions(Archive *fout, int *numPartitions)
{
PGresult *res;
int ntups;
int i;
PQExpBuffer query = createPQExpBuffer();
PartInfo *partinfo;
int i_partrelid;
int i_partparent;
int i_partbound;
/* Before version 10, there are no partitions */
if (fout->remoteVersion < 100000)
{
*numPartitions = 0;
return NULL;
}
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
/* find the inheritance and boundary information about partitions */
appendPQExpBufferStr(query,
"SELECT inhrelid as partrelid, inhparent AS partparent,"
" pg_get_expr(relpartbound, inhrelid) AS partbound"
" FROM pg_class c, pg_inherits"
" WHERE c.oid = inhrelid AND c.relispartition");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
*numPartitions = ntups;
partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
i_partrelid = PQfnumber(res, "partrelid");
i_partparent = PQfnumber(res, "partparent");
i_partbound = PQfnumber(res, "partbound");
for (i = 0; i < ntups; i++)
{
partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
}
PQclear(res);
destroyPQExpBuffer(query);
return partinfo;
}
/*
* getIndexes
* get information about every index on a dumpable table
@ -6933,6 +7011,47 @@ getTransforms(Archive *fout, int *numTransforms)
return transforminfo;
}
/*
* getTablePartitionKeyInfo -
* for each interesting partitioned table, read information about its
* partition key
*
* modifies tblinfo
*/
void
getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables)
{
PQExpBuffer q = createPQExpBuffer();
int i,
ntups;
PGresult *res;
/* No partitioned tables before 10 */
if (fout->remoteVersion < 100000)
return;
for (i = 0; i < numTables; i++)
{
TableInfo *tbinfo = &(tblinfo[i]);
/* Only partitioned tables have partition key */
if (tbinfo->relkind != RELKIND_PARTITIONED_TABLE)
continue;
/* Don't bother computing anything for non-target tables, either */
if (!tbinfo->dobj.dump)
continue;
resetPQExpBuffer(q);
appendPQExpBuffer(q, "SELECT pg_catalog.pg_get_partkeydef('%u'::pg_catalog.oid)",
tbinfo->dobj.catId.oid);
res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
Assert(ntups == 1);
tbinfo->partkeydef = pg_strdup(PQgetvalue(res, 0, 0));
}
}
/*
* getTableAttrs -
* for each interesting table, read info about its attributes
@ -14201,6 +14320,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
if (tbinfo->partitionOf && !dopt->binary_upgrade)
{
TableInfo *parentRel = tbinfo->partitionOf;
appendPQExpBuffer(q, " PARTITION OF ");
if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
appendPQExpBuffer(q, "%s.",
fmtId(parentRel->dobj.namespace->dobj.name));
appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
}
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@ -14229,8 +14359,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
/* Skip column if fully defined by reloftype */
if (tbinfo->reloftype &&
/*
* Skip column if fully defined by reloftype or the
* partition parent.
*/
if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@ -14259,7 +14392,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
if (tbinfo->reloftype && !dopt->binary_upgrade)
if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@ -14317,15 +14451,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
!dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
* empty, when not using the OF TYPE syntax.
* empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
if (tbinfo->partitiondef && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\n");
appendPQExpBufferStr(q, tbinfo->partitiondef);
}
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@ -14343,6 +14484,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@ -14403,7 +14547,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
tbinfo->relkind == RELKIND_FOREIGN_TABLE ||
tbinfo->relkind == RELKIND_PARTITIONED_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
{
@ -14421,7 +14566,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
if (tbinfo->relkind == RELKIND_RELATION)
if (tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@ -14490,6 +14636,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
if (tbinfo->partitionOf)
{
appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
fmtId(tbinfo->partitionOf->dobj.name),
tbinfo->dobj.name,
tbinfo->partitiondef);
}
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
@ -14638,6 +14793,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{

View File

@ -312,6 +312,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
@ -321,6 +322,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@ -459,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
/* PartInfo isn't a DumpableObject, just temporary state */
typedef struct _partInfo
{
Oid partrelid; /* OID of a partition */
Oid partparent; /* OID of its parent */
char *partdef; /* partition bound definition */
} PartInfo;
typedef struct _prsInfo
{
DumpableObject dobj;
@ -625,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
@ -649,5 +662,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
#endif /* PG_DUMP_H */

View File

@ -865,6 +865,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
" WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@ -874,6 +875,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
gettext_noop("table"), /* partitioned table */
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@ -954,7 +956,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
"WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
"WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@ -1600,8 +1602,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@ -1666,6 +1668,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
case 'P':
if (tableinfo.relpersistence == 'u')
printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
schemaname, relationname);
else
printfPQExpBuffer(&title, _("Table \"%s.%s\""),
schemaname, relationname);
break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@ -1679,8 +1689,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
headers[cols++] = gettext_noop("Collation");
headers[cols++] = gettext_noop("Nullable");
@ -1701,12 +1711,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f')
tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
tableinfo.relkind == 'm' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@ -1782,7 +1792,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f')
tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@ -1790,14 +1800,61 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
tableinfo.relkind == 'm' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
if (pset.sversion >= 90600)
{
/* Get the partition information */
PGresult *result;
char *parent_name;
char *partdef;
printfPQExpBuffer(&buf,
"SELECT inhparent::pg_catalog.regclass, pg_get_expr(c.relpartbound, inhrelid)"
" FROM pg_catalog.pg_class c"
" JOIN pg_catalog.pg_inherits"
" ON c.oid = inhrelid"
" WHERE c.oid = '%s' AND c.relispartition;", oid);
result = PSQLexec(buf.data);
if (!result)
goto error_return;
if (PQntuples(result) > 0)
{
parent_name = PQgetvalue(result, 0, 0);
partdef = PQgetvalue(result, 0, 1);
printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
partdef);
printTableAddFooter(&cont, tmpbuf.data);
PQclear(result);
}
}
if (tableinfo.relkind == 'P')
{
/* Get the partition key information */
PGresult *result;
char *partkeydef;
printfPQExpBuffer(&buf,
"SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
oid);
result = PSQLexec(buf.data);
if (!result || PQntuples(result) != 1)
goto error_return;
partkeydef = PQgetvalue(result, 0, 0);
printfPQExpBuffer(&tmpbuf, _("Partition key: %s"), partkeydef);
printTableAddFooter(&cont, tmpbuf.data);
PQclear(result);
}
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@ -1936,7 +1993,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f')
tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@ -2513,7 +2570,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f')
tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@ -2558,8 +2615,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
/* print inherited tables */
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
/* print inherited tables (exclude, if parent is a partitioned table) */
printfPQExpBuffer(&buf,
"SELECT c.oid::pg_catalog.regclass"
" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
" WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
" AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@ -2588,9 +2649,23 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
/* print child tables */
if (pset.sversion >= 80300)
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
/* print child tables (with additional info if partitions) */
if (pset.sversion >= 100000)
printfPQExpBuffer(&buf,
"SELECT c.oid::pg_catalog.regclass, pg_get_expr(c.relpartbound, c.oid)"
" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
" WHERE c.oid=i.inhrelid AND"
" i.inhparent = '%s' AND"
" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else if (pset.sversion >= 80300)
printfPQExpBuffer(&buf,
"SELECT c.oid::pg_catalog.regclass"
" FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
" WHERE c.oid=i.inhrelid AND"
" i.inhparent = '%s' AND"
" EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s')"
" ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
@ -2605,24 +2680,39 @@ describeOneTableDetails(const char *schemaname,
/* print the number of child tables, if any */
if (tuples > 0)
{
printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
if (tableinfo.relkind != 'P')
printfPQExpBuffer(&buf, _("Number of child tables: %d (Use \\d+ to list them.)"), tuples);
else
printfPQExpBuffer(&buf, _("Number of partitions: %d (Use \\d+ to list them.)"), tuples);
printTableAddFooter(&cont, buf.data);
}
}
else
{
/* display the list of child tables */
const char *ct = _("Child tables");
const char *ct = tableinfo.relkind != 'P' ? _("Child tables") : _("Partitions");
int ctw = pg_wcswidth(ct, strlen(ct), pset.encoding);
for (i = 0; i < tuples; i++)
{
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s",
ct, PQgetvalue(result, i, 0));
if (tableinfo.relkind != 'P')
{
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s",
ct, PQgetvalue(result, i, 0));
else
printfPQExpBuffer(&buf, "%*s %s",
ctw, "", PQgetvalue(result, i, 0));
}
else
printfPQExpBuffer(&buf, "%*s %s",
ctw, "", PQgetvalue(result, i, 0));
{
if (i == 0)
printfPQExpBuffer(&buf, "%s: %s %s",
ct, PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
else
printfPQExpBuffer(&buf, "%*s %s %s",
ctw, "", PQgetvalue(result, i, 0), PQgetvalue(result, i, 1));
}
if (i < tuples - 1)
appendPQExpBufferChar(&buf, ',');
@ -2717,7 +2807,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
if (relkind == 'r' || relkind == 'm' || relkind == 'i')
if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@ -3051,6 +3141,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
" WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@ -3062,6 +3153,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
gettext_noop("table"), /* partitioned table */
gettext_noop("Type"),
gettext_noop("Owner"));
@ -3100,7 +3192,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
appendPQExpBufferStr(&buf, "'r',");
appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)

View File

@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
"c.relkind IN ('r')",
"c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
"c.relkind IN ('r', 'f', 'v')",
"c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201612061
#define CATALOG_VERSION_NO 201612071
#endif

View File

@ -189,7 +189,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender,
extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
DependencyType self_behavior);
DependencyType self_behavior,
bool ignore_self);
extern ObjectClass getObjectClass(const ObjectAddress *object);

View File

@ -134,4 +134,15 @@ extern void CheckAttributeType(const char *attname,
List *containing_rowtypes,
bool allow_system_table_mods);
/* pg_partitioned_table catalog manipulation functions */
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
AttrNumber *partattrs,
List *partexprs,
Oid *partopclass,
Oid *partcollation);
extern void RemovePartitionKeyByRelId(Oid relid);
extern void StorePartitionBound(Relation rel, Node *bound);
#endif /* HEAP_H */

View File

@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
#define PartitionedRelidIndexId 3351
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES

View File

@ -0,0 +1,83 @@
/*-------------------------------------------------------------------------
*
* 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 "executor/tuptable.h"
#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/rel.h"
/*
* PartitionBoundInfo encapsulates a set of partition bounds. It is usually
* associated with partitioned tables as part of its partition descriptor.
*
* The internal structure is opaque outside partition.c.
*/
typedef struct PartitionBoundInfoData *PartitionBoundInfo;
/*
* Information about partitions of a partitioned table.
*/
typedef struct PartitionDescData
{
int nparts; /* Number of partitions */
Oid *oids; /* OIDs of partitions */
PartitionBoundInfo boundinfo; /* collection of partition bounds */
} PartitionDescData;
typedef struct PartitionDescData *PartitionDesc;
/*-----------------------
* PartitionDispatch - information about one partitioned table in a partition
* hiearchy required to route a tuple to one of its partitions
*
* reldesc Relation descriptor of the table
* key Partition key information of the table
* keystate Execution state required for expressions in the partition key
* partdesc Partition descriptor of the table
* indexes Array with partdesc->nparts members (for details on what
* individual members represent, see how they are set in
* RelationGetPartitionDispatchInfo())
*-----------------------
*/
typedef struct PartitionDispatchData
{
Relation reldesc;
PartitionKey key;
List *keystate; /* list of ExprState */
PartitionDesc partdesc;
int *indexes;
} PartitionDispatchData;
typedef struct PartitionDispatchData *PartitionDispatch;
extern void RelationBuildPartitionDesc(Relation relation);
extern bool partition_bounds_equal(PartitionKey key,
PartitionBoundInfo p1, PartitionBoundInfo p2);
extern void check_new_partition_bound(char *relname, Relation parent, Node *bound);
extern Oid get_partition_parent(Oid relid);
extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound);
extern List *RelationGetPartitionQual(Relation rel, bool recurse);
/* For tuple routing */
extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
int lockmode, int *num_parted,
List **leaf_part_oids);
extern int get_partition_for_tuple(PartitionDispatch *pd,
TupleTableSlot *slot,
EState *estate,
Oid *failed_at);
#endif /* PARTITION_H */

View File

@ -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("");
@ -161,6 +165,7 @@ DESCR("");
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#define RELKIND_MATVIEW 'm' /* materialized view */
#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */

View File

@ -0,0 +1,76 @@
/*-------------------------------------------------------------------------
*
* pg_partitioned_table.h
* definition of the system "partitioned table" relation
* along with the relation's initial contents.
*
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
* information from the DATA() statements.
*
*-------------------------------------------------------------------------
*/
#ifndef PG_PARTITIONED_TABLE_H
#define PG_PARTITIONED_TABLE_H
#include "catalog/genbki.h"
/* ----------------
* pg_partitioned_table definition. cpp turns this into
* typedef struct FormData_pg_partitioned_table
* ----------------
*/
#define PartitionedRelationId 3350
CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS
{
Oid partrelid; /* partitioned table oid */
char partstrat; /* partitioning strategy */
int16 partnatts; /* number of partition key columns */
/*
* variable-length fields start here, but we allow direct access to
* partattrs via the C struct. That's because the first variable-length
* field of a heap tuple can be reliably accessed using its C struct
* offset, as previous fields are all non-nullable fixed-length fields.
*/
int2vector partattrs; /* each member of the array is the
* attribute number of a partition key
* column, or 0 if the column is actually
* an expression */
#ifdef CATALOG_VARLEN
oidvector partclass; /* operator class to compare keys */
oidvector partcollation; /* user-specified collation for keys */
pg_node_tree partexprs; /* list of expressions in the partitioning
* key; one item for each zero entry in
* partattrs[] */
#endif
} FormData_pg_partitioned_table;
/* ----------------
* Form_pg_partitioned_table corresponds to a pointer to a tuple with
* the format of pg_partitioned_table relation.
* ----------------
*/
typedef FormData_pg_partitioned_table *Form_pg_partitioned_table;
/* ----------------
* compiler constants for pg_partitioned_table
* ----------------
*/
#define Natts_pg_partitioned_table 7
#define Anum_pg_partitioned_table_partrelid 1
#define Anum_pg_partitioned_table_partstrat 2
#define Anum_pg_partitioned_table_partnatts 3
#define Anum_pg_partitioned_table_partattrs 4
#define Anum_pg_partitioned_table_partclass 5
#define Anum_pg_partitioned_table_partcollation 6
#define Anum_pg_partitioned_table_partexprs 7
#endif /* PG_PARTITIONED_TABLE_H */

View File

@ -1979,6 +1979,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));

View File

@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
extern Oid ResolveOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);

View File

@ -23,7 +23,7 @@
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
ObjectAddress *typaddress);
ObjectAddress *typaddress, const char *queryString);
extern void RemoveRelations(DropStmt *drop);

View File

@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
PartitionDispatch *pd,
TupleTableSlot *slot,
EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);

View File

@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@ -320,6 +321,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
* PartitionCheck partition check expression
* PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@ -344,6 +347,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
List *ri_PartitionCheck;
List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
@ -1143,6 +1148,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
struct PartitionDispatchData **mt_partition_dispatch_info;
/* Tuple-routing support info */
int mt_num_dispatch; /* Number of entries in the above
* array */
int mt_num_partitions; /* Number of members in the
* following arrays */
ResultRelInfo *mt_partitions; /* Per partition result relation */
TupleConversionMap **mt_partition_tupconv_maps;
/* Per partition tuple conversion map */
} ModifyTableState;
/* ----------------

View File

@ -406,6 +406,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@ -454,6 +455,10 @@ typedef enum NodeTag
T_CommonTableExpr,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
T_PartitionRangeDatum,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)

View File

@ -699,6 +699,79 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
/* Partitioning related definitions */
/*
* PartitionElem - a column in the partition key
*/
typedef struct PartitionElem
{
NodeTag type;
char *name; /* name of column to partition on, or NULL */
Node *expr; /* expression to partition on, or NULL */
List *collation; /* name of collation; NIL = default */
List *opclass; /* name of desired opclass; NIL = default */
int location; /* token location, or -1 if unknown */
} PartitionElem;
/*
* PartitionSpec - partition key specification
*/
typedef struct PartitionSpec
{
NodeTag type;
char *strategy; /* partitioning strategy ('list' or 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
} PartitionSpec;
#define PARTITION_STRATEGY_LIST 'l'
#define PARTITION_STRATEGY_RANGE 'r'
/*
* PartitionBoundSpec - a partition bound specification
*/
typedef struct PartitionBoundSpec
{
NodeTag type;
char strategy;
/* List partition values */
List *listdatums;
/*
* Range partition lower and upper bounds; each member of the lists
* is a PartitionRangeDatum (see below).
*/
List *lowerdatums;
List *upperdatums;
int location;
} PartitionBoundSpec;
/*
* PartitionRangeDatum
*/
typedef struct PartitionRangeDatum
{
NodeTag type;
bool infinite;
Node *value;
int location;
} PartitionRangeDatum;
/*
* PartitionCmd - ALTER TABLE partition commands
*/
typedef struct PartitionCmd
{
NodeTag type;
RangeVar *name;
Node *bound;
} PartitionCmd;
/****************************************************************************
* Nodes for a Query tree
@ -1549,7 +1622,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
AT_GenericOptions /* OPTIONS (...) */
AT_GenericOptions, /* OPTIONS (...) */
AT_AttachPartition, /* ATTACH PARTITION */
AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@ -1775,6 +1850,8 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
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 */

View File

@ -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)

View File

@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */
} ParseExprKind;

View File

@ -25,5 +25,7 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
extern Node *transformPartitionBound(ParseState *pstate, Relation parent,
Node *bound);
#endif /* PARSE_UTILCMD_H */

View File

@ -45,6 +45,11 @@
*/
#define INDEX_MAX_KEYS 32
/*
* Maximum number of columns in a partition key
*/
#define PARTITION_MAX_KEYS 32
/*
* Set the upper and lower bounds of sequence values.
*/

View File

@ -728,6 +728,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);

View File

@ -45,6 +45,35 @@ typedef struct LockInfoData
typedef LockInfoData *LockInfo;
/*
* Information about the partition key of a relation
*/
typedef struct PartitionKeyData
{
char strategy; /* partitioning strategy */
int16 partnatts; /* number of columns in the partition key */
AttrNumber *partattrs; /* attribute numbers of columns in the
* partition key */
List *partexprs; /* list of expressions in the partitioning
* key, or NIL */
Oid *partopfamily; /* OIDs of operator families */
Oid *partopcintype; /* OIDs of opclass declared input data types */
FmgrInfo *partsupfunc; /* lookup info for support funcs */
/* Partitioning collation per attribute */
Oid *partcollation;
/* Type information per attribute */
Oid *parttypid;
int32 *parttypmod;
int16 *parttyplen;
bool *parttypbyval;
char *parttypalign;
Oid *parttypcoll;
} PartitionKeyData;
typedef struct PartitionKeyData *PartitionKey;
/*
* Here are the contents of a relation cache entry.
@ -94,6 +123,12 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
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 */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@ -534,6 +569,60 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
/*
* RelationGetPartitionKey
* Returns the PartitionKey of a relation
*/
#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
/*
* PartitionKey inquiry functions
*/
static inline int
get_partition_strategy(PartitionKey key)
{
return key->strategy;
}
static inline int
get_partition_natts(PartitionKey key)
{
return key->partnatts;
}
static inline List *
get_partition_exprs(PartitionKey key)
{
return key->partexprs;
}
/*
* PartitionKey inquiry functions - one column
*/
static inline int16
get_partition_col_attnum(PartitionKey key, int col)
{
return key->partattrs[col];
}
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);

View File

@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
PARTRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,

View File

@ -2974,3 +2974,346 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
c4 | integer | | |
DROP TABLE test_add_column;
-- unsupported constraint types for partitioned tables
CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
ALTER TABLE partitioned ADD UNIQUE (a);
ERROR: unique constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
^
ALTER TABLE partitioned ADD PRIMARY KEY (a);
ERROR: primary key constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
^
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ERROR: foreign key constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
^
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
ERROR: exclusion constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
^
-- cannot drop column that is part of the partition key
ALTER TABLE partitioned DROP COLUMN a;
ERROR: cannot drop column named in partition key
ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
ERROR: cannot alter type of column named in partition key
ALTER TABLE partitioned DROP COLUMN b;
ERROR: cannot drop column referenced in partition key expression
ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
ERROR: cannot alter type of column referenced in partition key expression
-- cannot drop NOT NULL on columns in the range partition key
ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
ERROR: column "a" is in range partition key
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE foo (
a int,
b int
);
ALTER TABLE partitioned INHERIT foo;
ERROR: cannot change inheritance of partitioned table
ALTER TABLE foo INHERIT partitioned;
ERROR: cannot inherit from partitioned table "partitioned"
-- cannot add NO INHERIT constraint to partitioned tables
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
DROP TABLE partitioned, foo;
--
-- ATTACH PARTITION
--
-- check that target table is partitioned
CREATE TABLE unparted (
a int
);
CREATE TABLE fail_part (like unparted);
ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
ERROR: "unparted" is not partitioned
DROP TABLE unparted, fail_part;
-- check that partition bound is compatible
CREATE TABLE list_parted (
a int NOT NULL,
b char(2) COLLATE "en_US",
CONSTRAINT check_a CHECK (a > 0)
) PARTITION BY LIST (a);
CREATE TABLE fail_part (LIKE list_parted);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
ERROR: invalid bound specification for a list partition
LINE 1: ...list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) T...
^
DROP TABLE fail_part;
-- check that the table being attached exists
ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
ERROR: relation "nonexistant" does not exist
-- check ownership of the source table
CREATE ROLE regress_test_me;
CREATE ROLE regress_test_not_me;
CREATE TABLE not_owned_by_me (LIKE list_parted);
ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
SET SESSION AUTHORIZATION regress_test_me;
CREATE TABLE owned_by_me (
a int
) PARTITION BY LIST (a);
ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
ERROR: must be owner of relation not_owned_by_me
RESET SESSION AUTHORIZATION;
DROP TABLE owned_by_me, not_owned_by_me;
DROP ROLE regress_test_not_me;
DROP ROLE regress_test_me;
-- check that the table being attached is not part of regular inheritance
CREATE TABLE parent (LIKE list_parted);
CREATE TABLE child () INHERITS (parent);
ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
ERROR: cannot attach inheritance child as partition
ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
ERROR: cannot attach inheritance parent as partition
DROP TABLE parent CASCADE;
NOTICE: drop cascades to table child
-- check any TEMP-ness
CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a);
CREATE TABLE perm_part (a int);
ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1);
ERROR: cannot attach a permanent relation as partition of temporary relation "temp_parted"
DROP TABLE temp_parted, perm_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: New partition should contain only the columns present in parent.
DROP TABLE fail_part;
-- check that the table being attached has every column of the parent
CREATE TABLE fail_part (a int NOT NULL);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ERROR: child table is missing column "b"
DROP TABLE fail_part;
-- check that columns match in type, collation and NOT NULL status
CREATE TABLE fail_part (
b char(3),
a int NOT NULL
);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ERROR: child table "fail_part" has different type for column "b"
ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ERROR: child table "fail_part" has different collation for column "b"
DROP TABLE fail_part;
-- check that the table being attached has all constraints of the parent
CREATE TABLE fail_part (
b char(2) COLLATE "en_US",
a int NOT NULL
);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ERROR: child table is missing constraint "check_a"
-- check that the constraint matches in definition with parent's constraint
ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ERROR: child table "fail_part" has different definition for check constraint "check_a"
DROP TABLE fail_part;
-- check the attributes and constraints after partition is attached
CREATE TABLE part_1 (
a int NOT NULL,
b char(2) COLLATE "en_US",
CONSTRAINT check_a CHECK (a > 0)
);
ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
-- attislocal and conislocal are always false for merged attributes and constraints respectively.
SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
attislocal | attinhcount
------------+-------------
f | 1
f | 1
(2 rows)
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
conislocal | coninhcount
------------+-------------
f | 1
(1 row)
-- check that the new partition won't overlap with an existing partition
CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ERROR: partition "fail_part" would overlap partition "part_1"
-- check validation when attaching list partitions
CREATE TABLE list_parted2 (
a int,
b char
) PARTITION BY LIST (a);
-- check that violating rows are correctly reported
CREATE TABLE part_2 (LIKE list_parted2);
INSERT INTO part_2 VALUES (3, 'a');
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
ERROR: partition constraint is violated by some row
-- should be ok after deleting the bad row
DELETE FROM part_2;
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
-- adding constraints that describe the desired partition constraint
-- (or more restrictive) will help skip the validation scan
CREATE TABLE part_3_4 (
LIKE list_parted2,
CONSTRAINT check_a CHECK (a IN (3))
);
-- however, if a list partition does not accept nulls, there should be
-- an explicit NOT NULL constraint on the partition key column for the
-- validation scan to be skipped;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
-- adding a NOT NULL constraint will cause the scan to be skipped
ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
ALTER TABLE part_3_4 ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
NOTICE: skipping scan to validate partition constraint
-- check validation when attaching range partitions
CREATE TABLE range_parted (
a int,
b int
) PARTITION BY RANGE (a, b);
-- check that violating rows are correctly reported
CREATE TABLE part1 (
a int NOT NULL CHECK (a = 1),
b int NOT NULL CHECK (b >= 1 AND b <= 10)
);
INSERT INTO part1 VALUES (1, 10);
-- Remember the TO bound is exclusive
ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
ERROR: partition constraint is violated by some row
-- should be ok after deleting the bad row
DELETE FROM part1;
ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
-- adding constraints that describe the desired partition constraint
-- (or more restrictive) will help skip the validation scan
CREATE TABLE part2 (
a int NOT NULL CHECK (a = 1),
b int NOT NULL CHECK (b >= 10 AND b < 18)
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
NOTICE: skipping scan to validate partition constraint
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
LIKE list_parted2
) PARTITION BY LIST (b);
-- check that violating rows are correctly reported
CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
INSERT INTO part_5_a (a, b) VALUES (6, 'a');
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
ERROR: partition constraint is violated by some row
-- delete the faulting row and also add a constraint to skip the scan
DELETE FROM part_5_a WHERE a NOT IN (3);
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
NOTICE: skipping scan to validate partition constraint
-- check that the table being attached is not already a partition
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
ERROR: "part_2" is already a partition
-- check that circular inheritance is not allowed
ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
ERROR: circular inheritance not allowed
DETAIL: "part_5" is already a child of "list_parted2".
ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
ERROR: circular inheritance not allowed
DETAIL: "list_parted2" is already a child of "list_parted2".
--
-- DETACH PARTITION
--
-- check that the partition being detached exists at all
ALTER TABLE list_parted2 DETACH PARTITION part_4;
ERROR: relation "part_4" does not exist
-- check that the partition being detached is actually a partition of the parent
CREATE TABLE not_a_part (a int);
ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
ERROR: relation "not_a_part" is not a partition of relation "list_parted2"
ALTER TABLE list_parted2 DETACH PARTITION part_1;
ERROR: relation "part_1" is not a partition of relation "list_parted2"
-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
-- attislocal/conislocal is set to true
ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
attinhcount | attislocal
-------------+------------
0 | t
0 | t
(2 rows)
SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
coninhcount | conislocal
-------------+------------
0 | t
(1 row)
DROP TABLE part_3_4;
-- Check ALTER TABLE commands for partitioned tables and partitions
-- cannot add/drop column to/from *only* the parent
ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
ERROR: column must be added to child tables too
ALTER TABLE ONLY list_parted2 DROP COLUMN b;
ERROR: column must be dropped from child tables too
-- cannot add a column to partition or drop an inherited one
ALTER TABLE part_2 ADD COLUMN c text;
ERROR: cannot add column to a partition
ALTER TABLE part_2 DROP COLUMN b;
ERROR: cannot drop inherited column "b"
-- Nor rename, alter type
ALTER TABLE part_2 RENAME COLUMN b to c;
ERROR: cannot rename inherited column "b"
ALTER TABLE part_2 ALTER COLUMN b TYPE text;
ERROR: cannot alter inherited column "b"
-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
ERROR: constraint must be added to child tables too
ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
ERROR: constraint must be added to child tables too
ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted2"
-- cannot drop inherited NOT NULL or check constraints from partition
ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
ALTER TABLE part_2 ALTER b DROP NOT NULL;
ERROR: column "b" is marked NOT NULL in parent table
ALTER TABLE part_2 DROP CONSTRAINT check_a2;
ERROR: cannot drop inherited constraint "check_a2" of relation "part_2"
-- cannot drop NOT NULL or check constraints from *only* the parent
ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
ERROR: constraint must be dropped from child tables too
ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
ERROR: constraint must be dropped from child tables too
-- check that a partition cannot participate in regular inheritance
CREATE TABLE inh_test () INHERITS (part_2);
ERROR: cannot inherit from partition "part_2"
CREATE TABLE inh_test (LIKE part_2);
ALTER TABLE inh_test INHERIT part_2;
ERROR: cannot inherit from a partition
ALTER TABLE part_2 INHERIT inh_test;
ERROR: cannot change inheritance of a partition
-- cannot drop or alter type of partition key columns of lower level
-- partitioned tables; for example, part_5, which is list_parted2's
-- partition, is partitioned on b;
ALTER TABLE list_parted2 DROP COLUMN b;
ERROR: cannot drop column named in partition key
ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
ERROR: cannot alter type of column named in partition key
-- cleanup
DROP TABLE list_parted, list_parted2, range_parted CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to table part1
drop cascades to table part2
drop cascades to table part_2
drop cascades to table part_5
drop cascades to table part_5_a
drop cascades to table part_1

View File

@ -253,3 +253,416 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
--
-- Partitioned tables
--
-- cannot combine INHERITS and PARTITION BY (although grammar allows)
CREATE TABLE partitioned (
a int
) INHERITS (some_table) PARTITION BY LIST (a);
ERROR: cannot create partitioned table as inheritance child
-- cannot use more than 1 column as partition key for list partitioned table
CREATE TABLE partitioned (
a1 int,
a2 int
) PARTITION BY LIST (a1, a2); -- fail
ERROR: cannot list partition using more than one column
-- unsupported constraint type for partitioned tables
CREATE TABLE partitioned (
a int PRIMARY KEY
) PARTITION BY RANGE (a);
ERROR: primary key constraints are not supported on partitioned tables
LINE 2: a int PRIMARY KEY
^
CREATE TABLE pkrel (
a int PRIMARY KEY
);
CREATE TABLE partitioned (
a int REFERENCES pkrel(a)
) PARTITION BY RANGE (a);
ERROR: foreign key constraints are not supported on partitioned tables
LINE 2: a int REFERENCES pkrel(a)
^
DROP TABLE pkrel;
CREATE TABLE partitioned (
a int UNIQUE
) PARTITION BY RANGE (a);
ERROR: unique constraints are not supported on partitioned tables
LINE 2: a int UNIQUE
^
CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
) PARTITION BY RANGE (a);
ERROR: exclusion constraints are not supported on partitioned tables
LINE 3: EXCLUDE USING gist (a WITH &&)
^
-- prevent column from being used twice in the partition key
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (a, a);
ERROR: column "a" appears more than once in partition key
-- prevent using prohibited expressions in the key
CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (retset(a));
ERROR: set-returning functions are not allowed in partition key expression
DROP FUNCTION retset(int);
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE ((avg(a)));
ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
ERROR: window functions are not allowed in partition key expression
CREATE TABLE partitioned (
a int
) PARTITION BY LIST ((a LIKE (SELECT 1)));
ERROR: cannot use subquery in partition key expression
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (('a'));
ERROR: cannot use constant expression as partition key
CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (const_func());
ERROR: cannot use constant expression as partition key
DROP FUNCTION const_func();
-- only accept "list" and "range" as partitioning strategy
CREATE TABLE partitioned (
a int
) PARTITION BY HASH (a);
ERROR: unrecognized partitioning strategy "hash"
-- specified column must be present in the table
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (b);
ERROR: column "b" named in partition key does not exist
-- cannot use system columns in partition key
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (xmin);
ERROR: cannot use system column "xmin" in partition key
-- functions in key must be immutable
CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (immut_func(a));
ERROR: functions in partition key expression must be marked IMMUTABLE
DROP FUNCTION immut_func(int);
-- cannot contain whole-row references
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE ((partitioned));
ERROR: partition key expressions cannot contain whole-row references
-- prevent using columns of unsupported types in key (type must have a btree operator class)
CREATE TABLE partitioned (
a point
) PARTITION BY LIST (a);
ERROR: data type point has no default btree operator class
HINT: You must specify a btree operator class or define a default btree operator class for the data type.
CREATE TABLE partitioned (
a point
) PARTITION BY LIST (a point_ops);
ERROR: operator class "point_ops" does not exist for access method "btree"
CREATE TABLE partitioned (
a point
) PARTITION BY RANGE (a);
ERROR: data type point has no default btree operator class
HINT: You must specify a btree operator class or define a default btree operator class for the data type.
CREATE TABLE partitioned (
a point
) PARTITION BY RANGE (a point_ops);
ERROR: operator class "point_ops" does not exist for access method "btree"
-- cannot add NO INHERIT constraints to partitioned tables
CREATE TABLE partitioned (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned"
-- some checks after successful creation of a partitioned table
CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
CREATE TABLE partitioned (
a int,
b int,
c text,
d text
) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
-- check relkind
SELECT relkind FROM pg_class WHERE relname = 'partitioned';
relkind
---------
P
(1 row)
-- check that range partition key columns are marked NOT NULL
SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
attname | attnotnull
---------+------------
a | t
b | f
c | t
d | t
(4 rows)
-- prevent a function referenced in partition key from being dropped
DROP FUNCTION plusone(int);
ERROR: cannot drop function plusone(integer) because other objects depend on it
DETAIL: table partitioned depends on function plusone(integer)
HINT: Use DROP ... CASCADE to drop the dependent objects too.
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
ERROR: cannot inherit from partitioned table "partitioned2"
-- Partition key in describe output
\d partitioned
Table "public.partitioned"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | not null |
b | integer | | |
c | text | | not null |
d | text | | not null |
Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "en_US")
\d partitioned2
Table "public.partitioned2"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
Partition key: LIST ((a + 1))
DROP TABLE partitioned, partitioned2;
--
-- Partitions
--
-- check partition bound syntax
CREATE TABLE list_parted (
a int
) PARTITION BY LIST (a);
-- syntax allows only string literal, numeric literal and null to be
-- specified for a partition bound value
CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
ERROR: syntax error at or near "int"
LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
^
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
ERROR: syntax error at or near "::"
LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
^
-- syntax does not allow empty list of values for list partitions
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
ERROR: syntax error at or near ")"
LINE 1: ...E TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
^
-- trying to specify range for list partitioned table
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
ERROR: invalid bound specification for a list partition
LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
^
-- specified literal can't be cast to the partition column data type
CREATE TABLE bools (
a bool
) PARTITION BY LIST (a);
CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
ERROR: specified value cannot be cast to type "boolean" of column "a"
LINE 1: ...REATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
^
DROP TABLE bools;
CREATE TABLE range_parted (
a date
) PARTITION BY RANGE (a);
-- trying to specify list for range partitioned table
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
ERROR: invalid bound specification for a range partition
LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
^
-- each of start and end bounds must have same number of values as the
-- length of the partition key
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
ERROR: FROM must specify exactly one value per partitioning column
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
ERROR: TO must specify exactly one value per partitioning column
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
ERROR: cannot specify NULL in range bound
-- check if compatible with the specified parent
-- cannot create as partition of a non-partitioned table
CREATE TABLE unparted (
a int
);
CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
ERROR: "unparted" is not partitioned
DROP TABLE unparted;
-- cannot create a permanent rel as partition of a temp rel
CREATE TEMP TABLE temp_parted (
a int
) PARTITION BY LIST (a);
CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
ERROR: cannot create a permanent relation as partition of temporary relation "temp_parted"
DROP TABLE temp_parted;
-- cannot create a table with oids as partition of table without oids
CREATE TABLE no_oids_parted (
a int
) PARTITION BY RANGE (a) WITHOUT OIDS;
CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
ERROR: cannot create table with OIDs as partition of table without OIDs
DROP TABLE no_oids_parted;
-- likewise, the reverse if also true
CREATE TABLE oids_parted (
a int
) PARTITION BY RANGE (a) WITH OIDS;
CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
ERROR: cannot create table without OIDs as partition of table with OIDs
DROP TABLE oids_parted;
-- check for partition bound overlap and other invalid specifications
CREATE TABLE list_parted2 (
a varchar
) PARTITION BY LIST (a);
CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
ERROR: partition "fail_part" would overlap partition "part_null_z"
CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
ERROR: partition "fail_part" would overlap partition "part_ab"
CREATE TABLE range_parted2 (
a int
) PARTITION BY RANGE (a);
-- trying to create range partition with empty range
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
ERROR: cannot create range partition with empty range
-- note that the range '[1, 1)' has no elements
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
ERROR: cannot create range partition with empty range
CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
ERROR: partition "fail_part" would overlap partition "part0"
CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
ERROR: partition "fail_part" would overlap partition "part1"
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
b int
) PARTITION BY RANGE (a, (b+1));
CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
ERROR: partition "fail_part" would overlap partition "part00"
CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
ERROR: partition "fail_part" would overlap partition "part10"
-- 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 NOT NULL DEFAULT 1 CHECK (b >= 0),
CONSTRAINT check_a CHECK (length(a) > 0)
) FOR VALUES IN ('b');
NOTICE: merging constraint "check_a" with inherited definition
-- conislocal should be false for any merged constraints
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
conislocal | coninhcount
------------+-------------
f | 1
(1 row)
-- specify PARTITION BY for a partition
CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
ERROR: column "c" named in partition key does not exist
CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
-- create a level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
-- Partition bound in describe output
\d part_b
Table "public.part_b"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | text | | |
b | integer | | not null | 1
Partition of: parted FOR VALUES IN ('b')
Check constraints:
"check_a" CHECK (length(a) > 0)
"part_b_b_check" CHECK (b >= 0)
-- Both partition bound and partition key in describe output
\d part_c
Table "public.part_c"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | text | | |
b | integer | | not null | 0
Partition of: parted FOR VALUES IN ('c')
Partition key: RANGE (b)
Check constraints:
"check_a" CHECK (length(a) > 0)
Number of partitions: 1 (Use \d+ to list them.)
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
-- output could vary depending on the order in which partition oids are
-- returned.
\d parted
Table "public.parted"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | text | | |
b | integer | | not null | 0
Partition key: LIST (a)
Check constraints:
"check_a" CHECK (length(a) > 0)
Number of partitions: 3 (Use \d+ to list them.)
-- partitions cannot be dropped directly
DROP TABLE part_a;
-- need to specify CASCADE to drop partitions along with the parent
DROP TABLE parted;
ERROR: cannot drop table parted because other objects depend on it
DETAIL: table part_b depends on table parted
table part_c depends on table parted
table part_c_1_10 depends on table part_c
HINT: Use DROP ... CASCADE to drop the dependent objects too.
DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE;
NOTICE: drop cascades to 14 other objects
DETAIL: drop cascades to table part00
drop cascades to table part10
drop cascades to table part11
drop cascades to table part12
drop cascades to table part0
drop cascades to table part1
drop cascades to table part_null_z
drop cascades to table part_ab
drop cascades to table part_1
drop cascades to table part_2
drop cascades to table part_null
drop cascades to table part_b
drop cascades to table part_c
drop cascades to table part_c_1_10

View File

@ -1542,3 +1542,275 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
--
-- Check that constraint exclusion works correctly with partitions using
-- implicit constraints generated from the partition bound information.
--
create table list_parted (
a varchar
) partition by list (a);
create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
create table part_null_xy partition of list_parted for values in (null, 'xy');
explain (costs off) select * from list_parted;
QUERY PLAN
--------------------------------
Append
-> Seq Scan on list_parted
-> Seq Scan on part_ab_cd
-> Seq Scan on part_ef_gh
-> Seq Scan on part_null_xy
(5 rows)
explain (costs off) select * from list_parted where a is null;
QUERY PLAN
--------------------------------
Append
-> Seq Scan on list_parted
Filter: (a IS NULL)
-> Seq Scan on part_null_xy
Filter: (a IS NULL)
(5 rows)
explain (costs off) select * from list_parted where a is not null;
QUERY PLAN
---------------------------------
Append
-> Seq Scan on list_parted
Filter: (a IS NOT NULL)
-> Seq Scan on part_ab_cd
Filter: (a IS NOT NULL)
-> Seq Scan on part_ef_gh
Filter: (a IS NOT NULL)
-> Seq Scan on part_null_xy
Filter: (a IS NOT NULL)
(9 rows)
explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
QUERY PLAN
----------------------------------------------------------
Append
-> Seq Scan on list_parted
Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
-> Seq Scan on part_ab_cd
Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
-> Seq Scan on part_ef_gh
Filter: ((a)::text = ANY ('{ab,cd,ef}'::text[]))
(7 rows)
explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
QUERY PLAN
---------------------------------------------------------------------------------------
Append
-> Seq Scan on list_parted
Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-> Seq Scan on part_ab_cd
Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-> Seq Scan on part_ef_gh
Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-> Seq Scan on part_null_xy
Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
(9 rows)
explain (costs off) select * from list_parted where a = 'ab';
QUERY PLAN
------------------------------------------
Append
-> Seq Scan on list_parted
Filter: ((a)::text = 'ab'::text)
-> Seq Scan on part_ab_cd
Filter: ((a)::text = 'ab'::text)
(5 rows)
create table range_list_parted (
a int,
b char(2)
) partition by range (a);
create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
create table part_1_10_ab partition of part_1_10 for values in ('ab');
create table part_1_10_cd partition of part_1_10 for values in ('cd');
create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
create table part_10_20_ab partition of part_10_20 for values in ('ab');
create table part_10_20_cd partition of part_10_20 for values in ('cd');
create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
create table part_21_30_ab partition of part_21_30 for values in ('ab');
create table part_21_30_cd partition of part_21_30 for values in ('cd');
create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
create table part_40_inf_ab partition of part_40_inf for values in ('ab');
create table part_40_inf_cd partition of part_40_inf for values in ('cd');
create table part_40_inf_null partition of part_40_inf for values in (null);
explain (costs off) select * from range_list_parted;
QUERY PLAN
-------------------------------------
Append
-> Seq Scan on range_list_parted
-> Seq Scan on part_1_10
-> Seq Scan on part_10_20
-> Seq Scan on part_21_30
-> Seq Scan on part_40_inf
-> Seq Scan on part_1_10_ab
-> Seq Scan on part_1_10_cd
-> Seq Scan on part_10_20_ab
-> Seq Scan on part_10_20_cd
-> Seq Scan on part_21_30_ab
-> Seq Scan on part_21_30_cd
-> Seq Scan on part_40_inf_ab
-> Seq Scan on part_40_inf_cd
-> Seq Scan on part_40_inf_null
(15 rows)
explain (costs off) select * from range_list_parted where a = 5;
QUERY PLAN
-------------------------------------
Append
-> Seq Scan on range_list_parted
Filter: (a = 5)
-> Seq Scan on part_1_10
Filter: (a = 5)
-> Seq Scan on part_1_10_ab
Filter: (a = 5)
-> Seq Scan on part_1_10_cd
Filter: (a = 5)
(9 rows)
explain (costs off) select * from range_list_parted where b = 'ab';
QUERY PLAN
-------------------------------------
Append
-> Seq Scan on range_list_parted
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_1_10
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_10_20
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_21_30
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_40_inf
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_1_10_ab
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_10_20_ab
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_21_30_ab
Filter: (b = 'ab'::bpchar)
-> Seq Scan on part_40_inf_ab
Filter: (b = 'ab'::bpchar)
(19 rows)
explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
QUERY PLAN
-----------------------------------------------------------------
Append
-> Seq Scan on range_list_parted
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-> Seq Scan on part_1_10
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-> Seq Scan on part_10_20
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-> Seq Scan on part_21_30
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-> Seq Scan on part_1_10_ab
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-> Seq Scan on part_10_20_ab
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
-> Seq Scan on part_21_30_ab
Filter: ((a >= 3) AND (a <= 23) AND (b = 'ab'::bpchar))
(15 rows)
/* Should select no rows because range partition key cannot be null */
explain (costs off) select * from range_list_parted where a is null;
QUERY PLAN
--------------------------
Result
One-Time Filter: false
(2 rows)
/* Should only select rows from the null-accepting partition */
explain (costs off) select * from range_list_parted where b is null;
QUERY PLAN
-------------------------------------
Append
-> Seq Scan on range_list_parted
Filter: (b IS NULL)
-> Seq Scan on part_1_10
Filter: (b IS NULL)
-> Seq Scan on part_10_20
Filter: (b IS NULL)
-> Seq Scan on part_21_30
Filter: (b IS NULL)
-> Seq Scan on part_40_inf
Filter: (b IS NULL)
-> Seq Scan on part_40_inf_null
Filter: (b IS NULL)
(13 rows)
explain (costs off) select * from range_list_parted where a is not null and a < 67;
QUERY PLAN
------------------------------------------------
Append
-> Seq Scan on range_list_parted
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_1_10
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_10_20
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_21_30
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_40_inf
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_1_10_ab
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_1_10_cd
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_10_20_ab
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_10_20_cd
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_21_30_ab
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_21_30_cd
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_40_inf_ab
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_40_inf_cd
Filter: ((a IS NOT NULL) AND (a < 67))
-> Seq Scan on part_40_inf_null
Filter: ((a IS NOT NULL) AND (a < 67))
(29 rows)
explain (costs off) select * from range_list_parted where a >= 30;
QUERY PLAN
-------------------------------------
Append
-> Seq Scan on range_list_parted
Filter: (a >= 30)
-> Seq Scan on part_40_inf
Filter: (a >= 30)
-> Seq Scan on part_40_inf_ab
Filter: (a >= 30)
-> Seq Scan on part_40_inf_cd
Filter: (a >= 30)
-> Seq Scan on part_40_inf_null
Filter: (a >= 30)
(11 rows)
drop table list_parted cascade;
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table part_ab_cd
drop cascades to table part_ef_gh
drop cascades to table part_null_xy
drop table range_list_parted cascade;
NOTICE: drop cascades to 13 other objects
DETAIL: drop cascades to table part_1_10
drop cascades to table part_1_10_ab
drop cascades to table part_1_10_cd
drop cascades to table part_10_20
drop cascades to table part_10_20_ab
drop cascades to table part_10_20_cd
drop cascades to table part_21_30
drop cascades to table part_21_30_ab
drop cascades to table part_21_30_cd
drop cascades to table part_40_inf
drop cascades to table part_40_inf_ab
drop cascades to table part_40_inf_cd
drop cascades to table part_40_inf_null

View File

@ -160,3 +160,143 @@ Rules:
drop table inserttest2;
drop table inserttest;
drop type insert_test_type;
-- direct partition inserts should check partition bound constraint
create table range_parted (
a text,
b int
) partition by range (a, (b+0));
create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
-- fail
insert into part1 values ('a', 11);
ERROR: new row for relation "part1" violates partition constraint
DETAIL: Failing row contains (a, 11).
insert into part1 values ('b', 1);
ERROR: new row for relation "part1" violates partition constraint
DETAIL: Failing row contains (b, 1).
-- ok
insert into part1 values ('a', 1);
-- fail
insert into part4 values ('b', 21);
ERROR: new row for relation "part4" violates partition constraint
DETAIL: Failing row contains (b, 21).
insert into part4 values ('a', 10);
ERROR: new row for relation "part4" violates partition constraint
DETAIL: Failing row contains (a, 10).
-- ok
insert into part4 values ('b', 10);
-- fail (partition key a has a NOT NULL constraint)
insert into part1 values (null);
ERROR: null value in column "a" violates not-null constraint
DETAIL: Failing row contains (null, null).
-- fail (expression key (b+0) cannot be null either)
insert into part1 values (1);
ERROR: new row for relation "part1" violates partition constraint
DETAIL: Failing row contains (1, null).
create table list_parted (
a text,
b int
) partition by list (lower(a));
create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
create table part_null partition of list_parted FOR VALUES IN (null);
-- fail
insert into part_aa_bb values ('cc', 1);
ERROR: new row for relation "part_aa_bb" violates partition constraint
DETAIL: Failing row contains (cc, 1).
insert into part_aa_bb values ('AAa', 1);
ERROR: new row for relation "part_aa_bb" violates partition constraint
DETAIL: Failing row contains (AAa, 1).
insert into part_aa_bb values (null);
ERROR: new row for relation "part_aa_bb" violates partition constraint
DETAIL: Failing row contains (null, null).
-- ok
insert into part_cc_dd values ('cC', 1);
insert into part_null values (null, 0);
-- check in case of multi-level partitioned table
create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
-- fail
insert into part_ee_ff1 values ('EE', 11);
ERROR: new row for relation "part_ee_ff1" violates partition constraint
DETAIL: Failing row contains (EE, 11).
-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
insert into part_ee_ff1 values ('cc', 1);
ERROR: new row for relation "part_ee_ff1" violates partition constraint
DETAIL: Failing row contains (cc, 1).
-- ok
insert into part_ee_ff1 values ('ff', 1);
insert into part_ee_ff2 values ('ff', 11);
-- Check tuple routing for partitioned tables
-- fail
insert into range_parted values ('a', 0);
ERROR: no partition of relation "range_parted" found for row
DETAIL: Failing row contains (a, 0).
-- ok
insert into range_parted values ('a', 1);
insert into range_parted values ('a', 10);
-- fail
insert into range_parted values ('a', 20);
ERROR: no partition of relation "range_parted" found for row
DETAIL: Failing row contains (a, 20).
-- ok
insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
ERROR: range partition key of row contains null
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
part1 | a | 1
part1 | a | 1
part2 | a | 10
part3 | b | 1
part4 | b | 10
part4 | b | 10
(6 rows)
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
-- fail (partition of part_ee_ff not found in both cases)
insert into list_parted values ('EE', 0);
ERROR: no partition of relation "part_ee_ff" found for row
DETAIL: Failing row contains (EE, 0).
insert into part_ee_ff values ('EE', 0);
ERROR: no partition of relation "part_ee_ff" found for row
DETAIL: Failing row contains (EE, 0).
-- ok
insert into list_parted values ('EE', 1);
insert into part_ee_ff values ('EE', 10);
select tableoid::regclass, * from list_parted;
tableoid | a | b
-------------+----+----
part_aa_bb | aA |
part_cc_dd | cC | 1
part_null | | 0
part_null | | 1
part_ee_ff1 | ff | 1
part_ee_ff1 | EE | 1
part_ee_ff2 | ff | 11
part_ee_ff2 | EE | 10
(8 rows)
-- cleanup
drop table range_parted cascade;
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table part1
drop cascades to table part2
drop cascades to table part3
drop cascades to table part4
drop table list_parted cascade;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to table part_aa_bb
drop cascades to table part_cc_dd
drop cascades to table part_null
drop cascades to table part_ee_ff
drop cascades to table part_ee_ff1
drop cascades to table part_ee_ff2

View File

@ -120,6 +120,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
pg_partitioned_table|t
pg_pltemplate|t
pg_policy|t
pg_proc|t

View File

@ -198,3 +198,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
DROP TABLE update_test;
DROP TABLE upsert_test;
-- update to a partition should check partition bound constraint for the new tuple
create table range_parted (
a text,
b int
) partition by range (a, b);
create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
insert into part_a_1_a_10 values ('a', 1);
insert into part_b_10_b_20 values ('b', 10);
-- fail
update part_a_1_a_10 set a = 'b' where a = 'a';
ERROR: new row for relation "part_a_1_a_10" violates partition constraint
DETAIL: Failing row contains (b, 1).
update range_parted set b = b - 1 where b = 10;
ERROR: new row for relation "part_b_10_b_20" violates partition constraint
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
-- cleanup
drop table range_parted cascade;
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table part_a_1_a_10
drop cascades to table part_a_10_a_20
drop cascades to table part_b_1_b_10
drop cascades to table part_b_10_b_20

View File

@ -1875,3 +1875,297 @@ ALTER TABLE test_add_column
ADD COLUMN c4 integer;
\d test_add_column
DROP TABLE test_add_column;
-- unsupported constraint types for partitioned tables
CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
ALTER TABLE partitioned ADD UNIQUE (a);
ALTER TABLE partitioned ADD PRIMARY KEY (a);
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
-- cannot drop column that is part of the partition key
ALTER TABLE partitioned DROP COLUMN a;
ALTER TABLE partitioned ALTER COLUMN a TYPE char(5);
ALTER TABLE partitioned DROP COLUMN b;
ALTER TABLE partitioned ALTER COLUMN b TYPE char(5);
-- cannot drop NOT NULL on columns in the range partition key
ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL;
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE foo (
a int,
b int
);
ALTER TABLE partitioned INHERIT foo;
ALTER TABLE foo INHERIT partitioned;
-- cannot add NO INHERIT constraint to partitioned tables
ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT;
DROP TABLE partitioned, foo;
--
-- ATTACH PARTITION
--
-- check that target table is partitioned
CREATE TABLE unparted (
a int
);
CREATE TABLE fail_part (like unparted);
ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
DROP TABLE unparted, fail_part;
-- check that partition bound is compatible
CREATE TABLE list_parted (
a int NOT NULL,
b char(2) COLLATE "en_US",
CONSTRAINT check_a CHECK (a > 0)
) PARTITION BY LIST (a);
CREATE TABLE fail_part (LIKE list_parted);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES FROM (1) TO (10);
DROP TABLE fail_part;
-- check that the table being attached exists
ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1);
-- check ownership of the source table
CREATE ROLE regress_test_me;
CREATE ROLE regress_test_not_me;
CREATE TABLE not_owned_by_me (LIKE list_parted);
ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me;
SET SESSION AUTHORIZATION regress_test_me;
CREATE TABLE owned_by_me (
a int
) PARTITION BY LIST (a);
ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1);
RESET SESSION AUTHORIZATION;
DROP TABLE owned_by_me, not_owned_by_me;
DROP ROLE regress_test_not_me;
DROP ROLE regress_test_me;
-- check that the table being attached is not part of regular inheritance
CREATE TABLE parent (LIKE list_parted);
CREATE TABLE child () INHERITS (parent);
ALTER TABLE list_parted ATTACH PARTITION child FOR VALUES IN (1);
ALTER TABLE list_parted ATTACH PARTITION parent FOR VALUES IN (1);
DROP TABLE parent CASCADE;
-- check any TEMP-ness
CREATE TEMP TABLE temp_parted (a int) PARTITION BY LIST (a);
CREATE TABLE perm_part (a int);
ALTER TABLE temp_parted ATTACH PARTITION perm_part FOR VALUES IN (1);
DROP TABLE temp_parted, perm_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);
DROP TYPE mytype CASCADE;
-- check existence (or non-existence) of oid column
ALTER TABLE list_parted SET WITH OIDS;
CREATE TABLE fail_part (a int);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ALTER TABLE list_parted SET WITHOUT OIDS;
ALTER TABLE fail_part SET WITH OIDS;
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
DROP TABLE fail_part;
-- check that the table being attached has only columns present in the parent
CREATE TABLE fail_part (like list_parted, c int);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
DROP TABLE fail_part;
-- check that the table being attached has every column of the parent
CREATE TABLE fail_part (a int NOT NULL);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
DROP TABLE fail_part;
-- check that columns match in type, collation and NOT NULL status
CREATE TABLE fail_part (
b char(3),
a int NOT NULL
);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA";
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
DROP TABLE fail_part;
-- check that the table being attached has all constraints of the parent
CREATE TABLE fail_part (
b char(2) COLLATE "en_US",
a int NOT NULL
);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
-- check that the constraint matches in definition with parent's constraint
ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
DROP TABLE fail_part;
-- check the attributes and constraints after partition is attached
CREATE TABLE part_1 (
a int NOT NULL,
b char(2) COLLATE "en_US",
CONSTRAINT check_a CHECK (a > 0)
);
ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
-- attislocal and conislocal are always false for merged attributes and constraints respectively.
SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
-- check that the new partition won't overlap with an existing partition
CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
-- check validation when attaching list partitions
CREATE TABLE list_parted2 (
a int,
b char
) PARTITION BY LIST (a);
-- check that violating rows are correctly reported
CREATE TABLE part_2 (LIKE list_parted2);
INSERT INTO part_2 VALUES (3, 'a');
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
-- should be ok after deleting the bad row
DELETE FROM part_2;
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
-- adding constraints that describe the desired partition constraint
-- (or more restrictive) will help skip the validation scan
CREATE TABLE part_3_4 (
LIKE list_parted2,
CONSTRAINT check_a CHECK (a IN (3))
);
-- however, if a list partition does not accept nulls, there should be
-- an explicit NOT NULL constraint on the partition key column for the
-- validation scan to be skipped;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
-- adding a NOT NULL constraint will cause the scan to be skipped
ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
ALTER TABLE part_3_4 ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
-- check validation when attaching range partitions
CREATE TABLE range_parted (
a int,
b int
) PARTITION BY RANGE (a, b);
-- check that violating rows are correctly reported
CREATE TABLE part1 (
a int NOT NULL CHECK (a = 1),
b int NOT NULL CHECK (b >= 1 AND b <= 10)
);
INSERT INTO part1 VALUES (1, 10);
-- Remember the TO bound is exclusive
ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
-- should be ok after deleting the bad row
DELETE FROM part1;
ALTER TABLE range_parted ATTACH PARTITION part1 FOR VALUES FROM (1, 1) TO (1, 10);
-- adding constraints that describe the desired partition constraint
-- (or more restrictive) will help skip the validation scan
CREATE TABLE part2 (
a int NOT NULL CHECK (a = 1),
b int NOT NULL CHECK (b >= 10 AND b < 18)
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
LIKE list_parted2
) PARTITION BY LIST (b);
-- check that violating rows are correctly reported
CREATE TABLE part_5_a PARTITION OF part_5 FOR VALUES IN ('a');
INSERT INTO part_5_a (a, b) VALUES (6, 'a');
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-- delete the faulting row and also add a constraint to skip the scan
DELETE FROM part_5_a WHERE a NOT IN (3);
ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5);
-- check that the table being attached is not already a partition
ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2);
-- check that circular inheritance is not allowed
ALTER TABLE part_5 ATTACH PARTITION list_parted2 FOR VALUES IN ('b');
ALTER TABLE list_parted2 ATTACH PARTITION list_parted2 FOR VALUES IN (0);
--
-- DETACH PARTITION
--
-- check that the partition being detached exists at all
ALTER TABLE list_parted2 DETACH PARTITION part_4;
-- check that the partition being detached is actually a partition of the parent
CREATE TABLE not_a_part (a int);
ALTER TABLE list_parted2 DETACH PARTITION not_a_part;
ALTER TABLE list_parted2 DETACH PARTITION part_1;
-- check that, after being detached, attinhcount/coninhcount is dropped to 0 and
-- attislocal/conislocal is set to true
ALTER TABLE list_parted2 DETACH PARTITION part_3_4;
SELECT attinhcount, attislocal FROM pg_attribute WHERE attrelid = 'part_3_4'::regclass AND attnum > 0;
SELECT coninhcount, conislocal FROM pg_constraint WHERE conrelid = 'part_3_4'::regclass AND conname = 'check_a';
DROP TABLE part_3_4;
-- Check ALTER TABLE commands for partitioned tables and partitions
-- cannot add/drop column to/from *only* the parent
ALTER TABLE ONLY list_parted2 ADD COLUMN c int;
ALTER TABLE ONLY list_parted2 DROP COLUMN b;
-- cannot add a column to partition or drop an inherited one
ALTER TABLE part_2 ADD COLUMN c text;
ALTER TABLE part_2 DROP COLUMN b;
-- Nor rename, alter type
ALTER TABLE part_2 RENAME COLUMN b to c;
ALTER TABLE part_2 ALTER COLUMN b TYPE text;
-- cannot add NOT NULL or check constraints to *only* the parent (ie, non-inherited)
ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
ALTER TABLE ONLY list_parted2 add constraint check_b check (b <> 'zz');
ALTER TABLE list_parted2 add constraint check_b check (b <> 'zz') NO INHERIT;
-- cannot drop inherited NOT NULL or check constraints from partition
ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a > 0);
ALTER TABLE part_2 ALTER b DROP NOT NULL;
ALTER TABLE part_2 DROP CONSTRAINT check_a2;
-- cannot drop NOT NULL or check constraints from *only* the parent
ALTER TABLE ONLY list_parted2 ALTER a DROP NOT NULL;
ALTER TABLE ONLY list_parted2 DROP CONSTRAINT check_a2;
-- check that a partition cannot participate in regular inheritance
CREATE TABLE inh_test () INHERITS (part_2);
CREATE TABLE inh_test (LIKE part_2);
ALTER TABLE inh_test INHERIT part_2;
ALTER TABLE part_2 INHERIT inh_test;
-- cannot drop or alter type of partition key columns of lower level
-- partitioned tables; for example, part_5, which is list_parted2's
-- partition, is partitioned on b;
ALTER TABLE list_parted2 DROP COLUMN b;
ALTER TABLE list_parted2 ALTER COLUMN b TYPE text;
-- cleanup
DROP TABLE list_parted, list_parted2, range_parted CASCADE;

View File

@ -269,3 +269,318 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
--
-- Partitioned tables
--
-- cannot combine INHERITS and PARTITION BY (although grammar allows)
CREATE TABLE partitioned (
a int
) INHERITS (some_table) PARTITION BY LIST (a);
-- cannot use more than 1 column as partition key for list partitioned table
CREATE TABLE partitioned (
a1 int,
a2 int
) PARTITION BY LIST (a1, a2); -- fail
-- unsupported constraint type for partitioned tables
CREATE TABLE partitioned (
a int PRIMARY KEY
) PARTITION BY RANGE (a);
CREATE TABLE pkrel (
a int PRIMARY KEY
);
CREATE TABLE partitioned (
a int REFERENCES pkrel(a)
) PARTITION BY RANGE (a);
DROP TABLE pkrel;
CREATE TABLE partitioned (
a int UNIQUE
) PARTITION BY RANGE (a);
CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
) PARTITION BY RANGE (a);
-- prevent column from being used twice in the partition key
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (a, a);
-- prevent using prohibited expressions in the key
CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (retset(a));
DROP FUNCTION retset(int);
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE ((avg(a)));
CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
CREATE TABLE partitioned (
a int
) PARTITION BY LIST ((a LIKE (SELECT 1)));
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (('a'));
CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE;
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (const_func());
DROP FUNCTION const_func();
-- only accept "list" and "range" as partitioning strategy
CREATE TABLE partitioned (
a int
) PARTITION BY HASH (a);
-- specified column must be present in the table
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (b);
-- cannot use system columns in partition key
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (xmin);
-- functions in key must be immutable
CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL;
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE (immut_func(a));
DROP FUNCTION immut_func(int);
-- cannot contain whole-row references
CREATE TABLE partitioned (
a int
) PARTITION BY RANGE ((partitioned));
-- prevent using columns of unsupported types in key (type must have a btree operator class)
CREATE TABLE partitioned (
a point
) PARTITION BY LIST (a);
CREATE TABLE partitioned (
a point
) PARTITION BY LIST (a point_ops);
CREATE TABLE partitioned (
a point
) PARTITION BY RANGE (a);
CREATE TABLE partitioned (
a point
) PARTITION BY RANGE (a point_ops);
-- cannot add NO INHERIT constraints to partitioned tables
CREATE TABLE partitioned (
a int,
CONSTRAINT check_a CHECK (a > 0) NO INHERIT
) PARTITION BY RANGE (a);
-- some checks after successful creation of a partitioned table
CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL;
CREATE TABLE partitioned (
a int,
b int,
c text,
d text
) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US");
-- check relkind
SELECT relkind FROM pg_class WHERE relname = 'partitioned';
-- check that range partition key columns are marked NOT NULL
SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0;
-- prevent a function referenced in partition key from being dropped
DROP FUNCTION plusone(int);
-- partitioned table cannot partiticipate in regular inheritance
CREATE TABLE partitioned2 (
a int
) PARTITION BY LIST ((a+1));
CREATE TABLE fail () INHERITS (partitioned2);
-- Partition key in describe output
\d partitioned
\d partitioned2
DROP TABLE partitioned, partitioned2;
--
-- Partitions
--
-- check partition bound syntax
CREATE TABLE list_parted (
a int
) PARTITION BY LIST (a);
-- syntax allows only string literal, numeric literal and null to be
-- specified for a partition bound value
CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1');
CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2);
CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1');
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int);
-- syntax does not allow empty list of values for list partitions
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ();
-- trying to specify range for list partitioned table
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
-- specified literal can't be cast to the partition column data type
CREATE TABLE bools (
a bool
) PARTITION BY LIST (a);
CREATE TABLE bools_true PARTITION OF bools FOR VALUES IN (1);
DROP TABLE bools;
CREATE TABLE range_parted (
a date
) PARTITION BY RANGE (a);
-- trying to specify list for range partitioned table
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
-- each of start and end bounds must have same number of values as the
-- length of the partition key
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
-- cannot specify null values in range bounds
CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
-- check if compatible with the specified parent
-- cannot create as partition of a non-partitioned table
CREATE TABLE unparted (
a int
);
CREATE TABLE fail_part PARTITION OF unparted FOR VALUES IN ('a');
DROP TABLE unparted;
-- cannot create a permanent rel as partition of a temp rel
CREATE TEMP TABLE temp_parted (
a int
) PARTITION BY LIST (a);
CREATE TABLE fail_part PARTITION OF temp_parted FOR VALUES IN ('a');
DROP TABLE temp_parted;
-- cannot create a table with oids as partition of table without oids
CREATE TABLE no_oids_parted (
a int
) PARTITION BY RANGE (a) WITHOUT OIDS;
CREATE TABLE fail_part PARTITION OF no_oids_parted FOR VALUES FROM (1) TO (10 )WITH OIDS;
DROP TABLE no_oids_parted;
-- likewise, the reverse if also true
CREATE TABLE oids_parted (
a int
) PARTITION BY RANGE (a) WITH OIDS;
CREATE TABLE fail_part PARTITION OF oids_parted FOR VALUES FROM (1) TO (10 ) WITHOUT OIDS;
DROP TABLE oids_parted;
-- check for partition bound overlap and other invalid specifications
CREATE TABLE list_parted2 (
a varchar
) PARTITION BY LIST (a);
CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z');
CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b');
CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null);
CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c');
CREATE TABLE range_parted2 (
a int
) PARTITION BY RANGE (a);
-- trying to create range partition with empty range
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (0);
-- note that the range '[1, 1)' has no elements
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
b int
) PARTITION BY RANGE (a, (b+1));
CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
-- check schema propagation from parent
CREATE TABLE parted (
a text,
b int NOT NULL DEFAULT 0,
CONSTRAINT check_a CHECK (length(a) > 0)
) PARTITION BY LIST (a);
CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a');
-- only inherited attributes (never local ones)
SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0;
-- able to specify column default, column constraint, and table constraint
CREATE TABLE part_b PARTITION OF parted (
b NOT NULL DEFAULT 1 CHECK (b >= 0),
CONSTRAINT check_a CHECK (length(a) > 0)
) FOR VALUES IN ('b');
-- conislocal should be false for any merged constraints
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a';
-- specify PARTITION BY for a partition
CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ((b));
-- create a level-2 partition
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
-- Partition bound in describe output
\d part_b
-- Both partition bound and partition key in describe output
\d part_c
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
-- output could vary depending on the order in which partition oids are
-- returned.
\d parted
-- partitions cannot be dropped directly
DROP TABLE part_a;
-- 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;

View File

@ -536,3 +536,55 @@ FROM generate_series(1, 3) g(i);
reset enable_seqscan;
reset enable_indexscan;
reset enable_bitmapscan;
--
-- Check that constraint exclusion works correctly with partitions using
-- implicit constraints generated from the partition bound information.
--
create table list_parted (
a varchar
) partition by list (a);
create table part_ab_cd partition of list_parted for values in ('ab', 'cd');
create table part_ef_gh partition of list_parted for values in ('ef', 'gh');
create table part_null_xy partition of list_parted for values in (null, 'xy');
explain (costs off) select * from list_parted;
explain (costs off) select * from list_parted where a is null;
explain (costs off) select * from list_parted where a is not null;
explain (costs off) select * from list_parted where a in ('ab', 'cd', 'ef');
explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd');
explain (costs off) select * from list_parted where a = 'ab';
create table range_list_parted (
a int,
b char(2)
) partition by range (a);
create table part_1_10 partition of range_list_parted for values from (1) to (10) partition by list (b);
create table part_1_10_ab partition of part_1_10 for values in ('ab');
create table part_1_10_cd partition of part_1_10 for values in ('cd');
create table part_10_20 partition of range_list_parted for values from (10) to (20) partition by list (b);
create table part_10_20_ab partition of part_10_20 for values in ('ab');
create table part_10_20_cd partition of part_10_20 for values in ('cd');
create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
create table part_21_30_ab partition of part_21_30 for values in ('ab');
create table part_21_30_cd partition of part_21_30 for values in ('cd');
create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
create table part_40_inf_ab partition of part_40_inf for values in ('ab');
create table part_40_inf_cd partition of part_40_inf for values in ('cd');
create table part_40_inf_null partition of part_40_inf for values in (null);
explain (costs off) select * from range_list_parted;
explain (costs off) select * from range_list_parted where a = 5;
explain (costs off) select * from range_list_parted where b = 'ab';
explain (costs off) select * from range_list_parted where a between 3 and 23 and b in ('ab');
/* Should select no rows because range partition key cannot be null */
explain (costs off) select * from range_list_parted where a is null;
/* Should only select rows from the null-accepting partition */
explain (costs off) select * from range_list_parted where b is null;
explain (costs off) select * from range_list_parted where a is not null and a < 67;
explain (costs off) select * from range_list_parted where a >= 30;
drop table list_parted cascade;
drop table range_list_parted cascade;

View File

@ -84,3 +84,89 @@ create rule irule3 as on insert to inserttest2 do also
drop table inserttest2;
drop table inserttest;
drop type insert_test_type;
-- direct partition inserts should check partition bound constraint
create table range_parted (
a text,
b int
) partition by range (a, (b+0));
create table part1 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part2 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part3 partition of range_parted for values from ('b', 1) to ('b', 10);
create table part4 partition of range_parted for values from ('b', 10) to ('b', 20);
-- fail
insert into part1 values ('a', 11);
insert into part1 values ('b', 1);
-- ok
insert into part1 values ('a', 1);
-- fail
insert into part4 values ('b', 21);
insert into part4 values ('a', 10);
-- ok
insert into part4 values ('b', 10);
-- fail (partition key a has a NOT NULL constraint)
insert into part1 values (null);
-- fail (expression key (b+0) cannot be null either)
insert into part1 values (1);
create table list_parted (
a text,
b int
) partition by list (lower(a));
create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb');
create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd');
create table part_null partition of list_parted FOR VALUES IN (null);
-- fail
insert into part_aa_bb values ('cc', 1);
insert into part_aa_bb values ('AAa', 1);
insert into part_aa_bb values (null);
-- ok
insert into part_cc_dd values ('cC', 1);
insert into part_null values (null, 0);
-- check in case of multi-level partitioned table
create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b);
create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10);
create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20);
-- fail
insert into part_ee_ff1 values ('EE', 11);
-- fail (even the parent's, ie, part_ee_ff's partition constraint applies)
insert into part_ee_ff1 values ('cc', 1);
-- ok
insert into part_ee_ff1 values ('ff', 1);
insert into part_ee_ff2 values ('ff', 11);
-- Check tuple routing for partitioned tables
-- fail
insert into range_parted values ('a', 0);
-- ok
insert into range_parted values ('a', 1);
insert into range_parted values ('a', 10);
-- fail
insert into range_parted values ('a', 20);
-- ok
insert into range_parted values ('b', 1);
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
-- fail (partition of part_ee_ff not found in both cases)
insert into list_parted values ('EE', 0);
insert into part_ee_ff values ('EE', 0);
-- ok
insert into list_parted values ('EE', 1);
insert into part_ee_ff values ('EE', 10);
select tableoid::regclass, * from list_parted;
-- cleanup
drop table range_parted cascade;
drop table list_parted cascade;

View File

@ -106,3 +106,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
DROP TABLE update_test;
DROP TABLE upsert_test;
-- update to a partition should check partition bound constraint for the new tuple
create table range_parted (
a text,
b int
) partition by range (a, b);
create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
insert into part_a_1_a_10 values ('a', 1);
insert into part_b_10_b_20 values ('b', 10);
-- fail
update part_a_1_a_10 set a = 'b' where a = 'a';
update range_parted set b = b - 1 where b = 10;
-- ok
update range_parted set b = b + 1 where b = 10;
-- cleanup
drop table range_parted cascade;

View File

@ -1469,6 +1469,11 @@ ParsedText
ParsedWord
ParserSetupHook
ParserState
PartitionBoundInfoData
PartitionBoundSpec
PartitionCmd
PartitionListValue
PartitionRangeBound
Path
PathClauseUsage
PathCostComparison
@ -1660,6 +1665,7 @@ RWConflictPoolHeader
Range
RangeBound
RangeBox
RangeDatumContent
RangeFunction
RangeIOData
RangeQueryClause