Commit f0e44751 authored by Robert Haas's avatar Robert Haas

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.
parent b7e1ae23
...@@ -225,6 +225,11 @@ ...@@ -225,6 +225,11 @@
<entry>template data for procedural languages</entry> <entry>template data for procedural languages</entry>
</row> </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> <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry> <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry> <entry>row-security policies</entry>
...@@ -1723,7 +1728,8 @@ ...@@ -1723,7 +1728,8 @@
<entry><type>char</type></entry> <entry><type>char</type></entry>
<entry></entry> <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>S</> = sequence, <literal>v</> = view,
<literal>m</> = materialized view, <literal>m</> = materialized view,
<literal>c</> = composite type, <literal>t</> = TOAST table, <literal>c</> = composite type, <literal>t</> = TOAST table,
...@@ -1839,6 +1845,13 @@ ...@@ -1839,6 +1845,13 @@
</entry> </entry>
</row> </row>
<row>
<entry><structfield>relispartition</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>True if table is a partition</entry>
</row>
<row> <row>
<entry><structfield>relfrozenxid</structfield></entry> <entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry> <entry><type>xid</type></entry>
...@@ -1885,6 +1898,16 @@ ...@@ -1885,6 +1898,16 @@
Access-method-specific options, as <quote>keyword=value</> strings Access-method-specific options, as <quote>keyword=value</> strings
</entry> </entry>
</row> </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> </tbody>
</tgroup> </tgroup>
</table> </table>
...@@ -4689,6 +4712,110 @@ ...@@ -4689,6 +4712,110 @@
</sect1> </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"> <sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title> <title><structname>pg_policy</structname></title>
......
...@@ -33,6 +33,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ...@@ -33,6 +33,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
SET SCHEMA <replaceable class="PARAMETER">new_schema</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> [, ... ] ] 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 ] 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> <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> ...@@ -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 values or to reject null values. You can only use <literal>SET
NOT NULL</> when the column contains no null values. NOT NULL</> when the column contains no null values.
</para> </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> </listitem>
</varlistentry> </varlistentry>
...@@ -704,13 +714,63 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> ...@@ -704,13 +714,63 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem> </listitem>
</varlistentry> </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> </variablelist>
</para> </para>
<para> <para>
All the actions except <literal>RENAME</literal>, All the actions except <literal>RENAME</literal>,
<literal>SET TABLESPACE</literal> and <literal>SET SCHEMA</literal> <literal>SET TABLESPACE</literal>, <literal>SET SCHEMA</literal>,
can be combined into <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 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 is possible to add several columns and/or alter the type of several
columns in a single command. This is particularly useful with large 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> ...@@ -721,8 +781,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
You must own the table to use <command>ALTER TABLE</>. You must own the table to use <command>ALTER TABLE</>.
To change the schema or tablespace of a table, you must also have To change the schema or tablespace of a table, you must also have
<literal>CREATE</literal> privilege on the new schema or tablespace. <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 To add the table as a new child of a parent table, you must own the parent
parent table as well. 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 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 owning role, and that role must have <literal>CREATE</literal> privilege on
the table's schema. (These restrictions enforce that altering the owner 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> ...@@ -938,6 +999,25 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem> </listitem>
</varlistentry> </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> </variablelist>
</refsect1> </refsect1>
...@@ -977,6 +1057,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> ...@@ -977,6 +1057,11 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
but does not require a table rewrite. but does not require a table rewrite.
</para> </para>
<para>
Similarly, when attaching a new partition it may be scanned to verify that
existing rows meet the partition constraint.
</para>
<para> <para>
The main reason for providing the option to specify multiple changes The main reason for providing the option to specify multiple changes
in a single <command>ALTER TABLE</> is that multiple table scans or 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> ...@@ -1047,6 +1132,9 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP COLUMN</literal> (i.e., <command>ALTER TABLE ONLY ... DROP
COLUMN</command>) never removes any descendant columns, but COLUMN</command>) never removes any descendant columns, but
instead marks them as independently defined rather than inherited. instead marks them as independently defined rather than inherited.
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>
<para> <para>
...@@ -1233,6 +1321,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey, ...@@ -1233,6 +1321,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey,
ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx; ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx;
</programlisting></para> </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>
<refsect1> <refsect1>
......
...@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name ...@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
SERVER <replaceable class="parameter">server_name</replaceable> SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</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> <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ] [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
...@@ -67,6 +76,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] ...@@ -67,6 +76,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
name as any existing data type in the same schema. name as any existing data type in the same schema.
</para> </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> <para>
To be able to create a foreign table, you must have <literal>USAGE</literal> To be able to create a foreign table, you must have <literal>USAGE</literal>
privilege on the foreign server, as well as <literal>USAGE</literal> privilege on the foreign server, as well as <literal>USAGE</literal>
...@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films ( ...@@ -314,6 +329,17 @@ CREATE FOREIGN TABLE films (
SERVER film_server; SERVER film_server;
</programlisting></para> </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>
<refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility"> <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility">
......
...@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI ...@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ] [, ... ]
] ) ] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ] [ 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 ] [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
...@@ -38,6 +39,18 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI ...@@ -38,6 +39,18 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> } | <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 ] [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
...@@ -70,6 +83,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI ...@@ -70,6 +83,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } { 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> <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>] [, ... ] ) ] [ 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 ...@@ -229,6 +247,51 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term> <term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem> <listitem>
...@@ -313,6 +376,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI ...@@ -313,6 +376,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term> <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem> <listitem>
...@@ -1368,6 +1471,57 @@ CREATE TABLE employees OF employee_type ( ...@@ -1368,6 +1471,57 @@ CREATE TABLE employees OF employee_type (
PRIMARY KEY (name), PRIMARY KEY (name),
salary WITH OPTIONS DEFAULT 1000 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> </programlisting></para>
</refsect1> </refsect1>
......
...@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, ...@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION: case RELKIND_RELATION:
case RELKIND_TOASTVALUE: case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW: case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
options = heap_reloptions(classForm->relkind, datum, false); options = heap_reloptions(classForm->relkind, datum, false);
break; break;
case RELKIND_VIEW: case RELKIND_VIEW:
...@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) ...@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts; return (bytea *) rdopts;
case RELKIND_RELATION: case RELKIND_RELATION:
case RELKIND_MATVIEW: case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default: default:
/* other relkinds are not supported */ /* other relkinds are not supported */
......
...@@ -11,7 +11,7 @@ top_builddir = ../../.. ...@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ 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_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ pg_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/,\ ...@@ -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_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.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_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 \ toasting.h indexing.h \
) )
......
...@@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames) ...@@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
objects = list_concat(objects, objs); objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE); objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
objects = list_concat(objects, objs); objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE);
objects = list_concat(objects, objs);
break; break;
case ACL_OBJECT_SEQUENCE: case ACL_OBJECT_SEQUENCE:
objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE); objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
......
...@@ -1352,7 +1352,8 @@ void ...@@ -1352,7 +1352,8 @@ void
recordDependencyOnSingleRelExpr(const ObjectAddress *depender, recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId, Node *expr, Oid relId,
DependencyType behavior, DependencyType behavior,
DependencyType self_behavior) DependencyType self_behavior,
bool ignore_self)
{ {
find_expr_references_context context; find_expr_references_context context;
RangeTblEntry rte; RangeTblEntry rte;
...@@ -1407,9 +1408,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, ...@@ -1407,9 +1408,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
context.addrs->numrefs = outrefs; context.addrs->numrefs = outrefs;
/* Record the self-dependencies */ /* Record the self-dependencies */
recordMultipleDependencies(depender, if (!ignore_self)
self_addrs->refs, self_addrs->numrefs, recordMultipleDependencies(depender,
self_behavior); self_addrs->refs, self_addrs->numrefs,
self_behavior);
free_object_addresses(self_addrs); free_object_addresses(self_addrs);
} }
......
This diff is collapsed.
...@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation, ...@@ -1043,7 +1043,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Expressions, (Node *) indexInfo->ii_Expressions,
heapRelationId, heapRelationId,
DEPENDENCY_NORMAL, DEPENDENCY_NORMAL,
DEPENDENCY_AUTO); DEPENDENCY_AUTO, false);
} }
/* Store dependencies on anything mentioned in predicate */ /* Store dependencies on anything mentioned in predicate */
...@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation, ...@@ -1053,7 +1053,7 @@ index_create(Relation heapRelation,
(Node *) indexInfo->ii_Predicate, (Node *) indexInfo->ii_Predicate,
heapRelationId, heapRelationId,
DEPENDENCY_NORMAL, DEPENDENCY_NORMAL,
DEPENDENCY_AUTO); DEPENDENCY_AUTO, false);
} }
} }
else else
......
...@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname, ...@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation)))); RelationGetRelationName(relation))));
break; break;
case OBJECT_TABLE: 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", errmsg("\"%s\" is not a table",
...@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid) ...@@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind) switch (relForm->relkind)
{ {
case RELKIND_RELATION: case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
appendStringInfo(buffer, _("table %s"), appendStringInfo(buffer, _("table %s"),
relname); relname);
break; break;
...@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId) ...@@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
switch (relForm->relkind) switch (relForm->relkind)
{ {
case RELKIND_RELATION: case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
appendStringInfoString(buffer, "table"); appendStringInfoString(buffer, "table");
break; break;
case RELKIND_INDEX: case RELKIND_INDEX:
......
This diff is collapsed.
...@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName, ...@@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName,
*/ */
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId, recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL, DEPENDENCY_NORMAL,
DEPENDENCY_NORMAL); DEPENDENCY_NORMAL, false);
} }
/* Post creation hook for new constraint */ /* Post creation hook for new constraint */
......
...@@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options, ...@@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation. * locked the relation.
*/ */
if (onerel->rd_rel->relkind == RELKIND_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 */ /* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows; acquirefunc = acquire_sample_rows;
...@@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, ...@@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */ /* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION || 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 */ /* Regular table, so use the regular row acquisition function */
acquirefunc = acquire_sample_rows; acquirefunc = acquire_sample_rows;
......
...@@ -161,6 +161,11 @@ typedef struct CopyStateData ...@@ -161,6 +161,11 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */ ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */ bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table; 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. * These variables are used to reduce overhead in textual COPY FROM.
...@@ -1397,6 +1402,71 @@ BeginCopy(ParseState *pstate, ...@@ -1397,6 +1402,71 @@ BeginCopy(ParseState *pstate,
(errcode(ERRCODE_UNDEFINED_COLUMN), (errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs", errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel)))); 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 else
{ {
...@@ -1751,6 +1821,12 @@ BeginCopyTo(ParseState *pstate, ...@@ -1751,6 +1821,12 @@ BeginCopyTo(ParseState *pstate,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"", errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel)))); 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 else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
...@@ -2249,6 +2325,7 @@ CopyFrom(CopyState cstate) ...@@ -2249,6 +2325,7 @@ CopyFrom(CopyState cstate)
Datum *values; Datum *values;
bool *nulls; bool *nulls;
ResultRelInfo *resultRelInfo; ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */ EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext; ExprContext *econtext;
TupleTableSlot *myslot; TupleTableSlot *myslot;
...@@ -2275,6 +2352,7 @@ CopyFrom(CopyState cstate) ...@@ -2275,6 +2352,7 @@ CopyFrom(CopyState cstate)
* only hint about them in the view case.) * only hint about them in the view case.)
*/ */
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION && if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
!(cstate->rel->trigdesc && !(cstate->rel->trigdesc &&
cstate->rel->trigdesc->trig_insert_instead_row)) cstate->rel->trigdesc->trig_insert_instead_row))
{ {
...@@ -2385,6 +2463,7 @@ CopyFrom(CopyState cstate) ...@@ -2385,6 +2463,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo, InitResultRelInfo(resultRelInfo,
cstate->rel, cstate->rel,
1, /* dummy rangetable index */ 1, /* dummy rangetable index */
true, /* do load partition check expression */
0); 0);
ExecOpenIndices(resultRelInfo, false); ExecOpenIndices(resultRelInfo, false);
...@@ -2407,11 +2486,13 @@ CopyFrom(CopyState cstate) ...@@ -2407,11 +2486,13 @@ CopyFrom(CopyState cstate)
* BEFORE/INSTEAD OF triggers, or we need to evaluate volatile default * BEFORE/INSTEAD OF triggers, or we need to evaluate volatile default
* expressions. Such triggers or expressions might query the table we're * expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been * 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 && if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row || (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) || resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
cstate->partition_dispatch_info != NULL ||
cstate->volatile_defexprs) cstate->volatile_defexprs)
{ {
useHeapMultiInsert = false; useHeapMultiInsert = false;
...@@ -2488,6 +2569,59 @@ CopyFrom(CopyState cstate) ...@@ -2488,6 +2569,59 @@ CopyFrom(CopyState cstate)
slot = myslot; slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false); 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; skip_tuple = false;
/* BEFORE ROW INSERT Triggers */ /* BEFORE ROW INSERT Triggers */
...@@ -2513,7 +2647,8 @@ CopyFrom(CopyState cstate) ...@@ -2513,7 +2647,8 @@ CopyFrom(CopyState cstate)
else else
{ {
/* Check the constraints of the tuple */ /* Check the constraints of the tuple */
if (cstate->rel->rd_att->constr) if (cstate->rel->rd_att->constr ||
resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate); ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert) if (useHeapMultiInsert)
...@@ -2546,7 +2681,8 @@ CopyFrom(CopyState cstate) ...@@ -2546,7 +2681,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL; List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */ /* 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) if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, recheckIndexes = ExecInsertIndexTuples(slot,
...@@ -2570,6 +2706,12 @@ CopyFrom(CopyState cstate) ...@@ -2570,6 +2706,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command. * tuples inserted by an INSERT command.
*/ */
processed++; processed++;
if (saved_resultRelInfo)
{
resultRelInfo = saved_resultRelInfo;
estate->es_result_relation_info = resultRelInfo;
}
} }
} }
...@@ -2607,6 +2749,32 @@ CopyFrom(CopyState cstate) ...@@ -2607,6 +2749,32 @@ CopyFrom(CopyState cstate)
ExecCloseIndices(resultRelInfo); 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); FreeExecutorState(estate);
/* /*
......
...@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into) ...@@ -112,7 +112,7 @@ create_ctas_internal(List *attrList, IntoClause *into)
* Create the relation. (This will error out if there's an existing view, * 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.) * 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 * If necessary, create a TOAST table for the target table. Note that
......
...@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo, ...@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId, char *accessMethodName, Oid accessMethodId,
bool amcanorder, bool amcanorder,
bool isconstraint); bool isconstraint);
static Oid GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId, static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames, List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint); bool primary, bool isconstraint);
...@@ -383,6 +381,11 @@ DefineIndex(Oid relationId, ...@@ -383,6 +381,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"", errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel)))); 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 else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
...@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo, ...@@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/* /*
* Identify the opclass to use. * Identify the opclass to use.
*/ */
classOidP[attn] = GetIndexOpClass(attribute->opclass, classOidP[attn] = ResolveOpClass(attribute->opclass,
atttype, atttype,
accessMethodName, accessMethodName,
accessMethodId); accessMethodId);
/* /*
* Identify the exclusion operator, if any. * Identify the exclusion operator, if any.
...@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo, ...@@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/* /*
* Resolve possibly-defaulted operator class specification * Resolve possibly-defaulted operator class specification
*
* Note: This is used to resolve operator class specification in index and
* partition key definitions.
*/ */
static Oid Oid
GetIndexOpClass(List *opclass, Oid attrType, ResolveOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId) char *accessMethodName, Oid accessMethodId)
{ {
char *schemaname; char *schemaname;
char *opcname; char *opcname;
......
...@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid, ...@@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */ * check */
/* Currently, we only allow plain tables to be locked */ /* Currently, we only allow plain tables to be locked */
if (relkind != RELKIND_RELATION) if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", errmsg("\"%s\" is not a table",
......
...@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, ...@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname))); rv->relname)));
/* Relation type MUST be a table. */ /* Relation type MUST be a table. */
if (relkind != RELKIND_RELATION) if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname))); errmsg("\"%s\" is not a table", rv->relname)));
...@@ -384,7 +384,8 @@ RemovePolicyById(Oid policy_id) ...@@ -384,7 +384,8 @@ RemovePolicyById(Oid policy_id)
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
rel = heap_open(relid, AccessExclusiveLock); 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", errmsg("\"%s\" is not a table",
......
...@@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt) ...@@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
......
...@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) ...@@ -234,7 +234,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
stmt->tablespacename = NULL; stmt->tablespacename = NULL;
stmt->if_not_exists = seq->if_not_exists; 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; seqoid = address.objectId;
Assert(seqoid != InvalidOid); Assert(seqoid != InvalidOid);
...@@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by) ...@@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */ /* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION || 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("referenced relation \"%s\" is not a table or foreign table", errmsg("referenced relation \"%s\" is not a table or foreign table",
......
This diff is collapsed.
...@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, ...@@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional * Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions. * 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 */ /* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE && if (stmt->timing != TRIGGER_TYPE_BEFORE &&
...@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, ...@@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table", errmsg("\"%s\" is a table",
RelationGetRelationName(rel)), RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers."))); 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) else if (rel->rd_rel->relkind == RELKIND_VIEW)
{ {
...@@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid) ...@@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid)
if (rel->rd_rel->relkind != RELKIND_RELATION && if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_VIEW && 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table", errmsg("\"%s\" is not a table, view, or foreign table",
...@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid, ...@@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */ /* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW && 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table", errmsg("\"%s\" is not a table, view, or foreign table",
......
...@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist) ...@@ -2107,7 +2107,8 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
/* /*
* Finally create the relation. This also creates the type. * 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; return address;
} }
......
...@@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params) ...@@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
*/ */
if (onerel->rd_rel->relkind != RELKIND_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_TOASTVALUE) onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{ {
ereport(WARNING, ereport(WARNING,
(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
......
...@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, ...@@ -228,7 +228,8 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* existing view, so we don't need more code to complain if "replace" * existing view, so we don't need more code to complain if "replace"
* is false). * is false).
*/ */
address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL); address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL,
NULL);
Assert(address.objectId != InvalidOid); Assert(address.objectId != InvalidOid);
return address; return address;
} }
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include "access/transam.h" #include "access/transam.h"
#include "access/xact.h" #include "access/xact.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "catalog/partition.h"
#include "commands/matview.h" #include "commands/matview.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/execdebug.h" #include "executor/execdebug.h"
...@@ -825,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) ...@@ -825,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo, InitResultRelInfo(resultRelInfo,
resultRelation, resultRelation,
resultRelationIndex, resultRelationIndex,
true,
estate->es_instrument); estate->es_instrument);
resultRelInfo++; resultRelInfo++;
} }
...@@ -1019,6 +1021,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation) ...@@ -1019,6 +1021,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind) switch (resultRel->rd_rel->relkind)
{ {
case RELKIND_RELATION: case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
/* OK */ /* OK */
break; break;
case RELKIND_SEQUENCE: case RELKIND_SEQUENCE:
...@@ -1152,6 +1155,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType) ...@@ -1152,6 +1155,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind) switch (rel->rd_rel->relkind)
{ {
case RELKIND_RELATION: case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
/* OK */ /* OK */
break; break;
case RELKIND_SEQUENCE: case RELKIND_SEQUENCE:
...@@ -1212,6 +1216,7 @@ void ...@@ -1212,6 +1216,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo, InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc, Relation resultRelationDesc,
Index resultRelationIndex, Index resultRelationIndex,
bool load_partition_check,
int instrument_options) int instrument_options)
{ {
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
...@@ -1249,6 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, ...@@ -1249,6 +1254,10 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_projectReturning = NULL;
if (load_partition_check)
resultRelInfo->ri_PartitionCheck =
RelationGetPartitionQual(resultRelationDesc,
true);
} }
/* /*
...@@ -1311,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) ...@@ -1311,6 +1320,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo, InitResultRelInfo(rInfo,
rel, rel,
0, /* dummy rangetable index */ 0, /* dummy rangetable index */
true,
estate->es_instrument); estate->es_instrument);
estate->es_trig_target_relations = estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo); lappend(estate->es_trig_target_relations, rInfo);
...@@ -1691,6 +1701,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, ...@@ -1691,6 +1701,46 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL; 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 void
ExecConstraints(ResultRelInfo *resultRelInfo, ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate) TupleTableSlot *slot, EState *estate)
...@@ -1702,9 +1752,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo, ...@@ -1702,9 +1752,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols; Bitmapset *insertedCols;
Bitmapset *updatedCols; 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 natts = tupdesc->natts;
int attrChk; int attrChk;
...@@ -1735,7 +1785,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo, ...@@ -1735,7 +1785,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
} }
} }
if (constr->num_check > 0) if (constr && constr->num_check > 0)
{ {
const char *failed; const char *failed;
...@@ -1759,6 +1809,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo, ...@@ -1759,6 +1809,26 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed))); 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) ...@@ -2926,3 +2996,52 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL; epqstate->planstate = NULL;
epqstate->origslot = 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;
}
...@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -258,6 +258,7 @@ ExecInsert(ModifyTableState *mtstate,
{ {
HeapTuple tuple; HeapTuple tuple;
ResultRelInfo *resultRelInfo; ResultRelInfo *resultRelInfo;
ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc; Relation resultRelationDesc;
Oid newId; Oid newId;
List *recheckIndexes = NIL; List *recheckIndexes = NIL;
...@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -272,6 +273,56 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation * get information on the (current) result relation
*/ */
resultRelInfo = estate->es_result_relation_info; 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; resultRelationDesc = resultRelInfo->ri_RelationDesc;
/* /*
...@@ -369,7 +420,7 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -369,7 +420,7 @@ ExecInsert(ModifyTableState *mtstate,
/* /*
* Check the constraints of the tuple * Check the constraints of the tuple
*/ */
if (resultRelationDesc->rd_att->constr) if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate); ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0) if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
...@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate, ...@@ -511,6 +562,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes); 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 * Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness * required to do this after testing all constraints and uniqueness
...@@ -922,7 +979,7 @@ lreplace:; ...@@ -922,7 +979,7 @@ lreplace:;
/* /*
* Check the constraints of the tuple * Check the constraints of the tuple
*/ */
if (resultRelationDesc->rd_att->constr) if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate); ExecConstraints(resultRelInfo, slot, estate);
/* /*
...@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ...@@ -1565,6 +1622,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan; Plan *subplan;
ListCell *l; ListCell *l;
int i; int i;
Relation rel;
/* check for unsupported flags */ /* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
...@@ -1655,6 +1713,75 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ...@@ -1655,6 +1713,75 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo; 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. * Initialize any WITH CHECK OPTION constraints if needed.
*/ */
...@@ -1886,7 +2013,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ...@@ -1886,7 +2013,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION || if (relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW) relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE)
{ {
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo)) if (!AttributeNumberIsValid(j->jf_junkAttNo))
...@@ -1971,6 +2099,26 @@ ExecEndModifyTable(ModifyTableState *node) ...@@ -1971,6 +2099,26 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo); 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 * Free the exprcontext
*/ */
......
...@@ -3030,6 +3030,8 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) ...@@ -3030,6 +3030,8 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation); COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts); COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations); COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(ofTypename); COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints); COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options); COPY_NODE_FIELD(options);
...@@ -4188,6 +4190,70 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from) ...@@ -4188,6 +4190,70 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode; 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 * pg_list.h copy functions
* **************************************************************** * ****************************************************************
...@@ -5105,6 +5171,21 @@ copyObject(const void *from) ...@@ -5105,6 +5171,21 @@ copyObject(const void *from)
case T_TriggerTransition: case T_TriggerTransition:
retval = _copyTriggerTransition(from); retval = _copyTriggerTransition(from);
break; 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 * MISCELLANEOUS NODES
......
...@@ -1168,6 +1168,8 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b) ...@@ -1168,6 +1168,8 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts); COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations); COMPARE_NODE_FIELD(inhRelations);
COMPARE_NODE_FIELD(partspec);
COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(ofTypename); COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints); COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options); COMPARE_NODE_FIELD(options);
...@@ -2646,6 +2648,59 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b) ...@@ -2646,6 +2648,59 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b)
return true; 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 * Stuff from pg_list.h
*/ */
...@@ -3402,6 +3457,21 @@ equal(const void *a, const void *b) ...@@ -3402,6 +3457,21 @@ equal(const void *a, const void *b)
case T_TriggerTransition: case T_TriggerTransition:
retval = _equalTriggerTransition(a, b); retval = _equalTriggerTransition(a, b);
break; 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: default:
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
......
...@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr) ...@@ -1552,6 +1552,12 @@ exprLocation(const Node *expr)
/* just use nested expr's location */ /* just use nested expr's location */
loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr); loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
break; break;
case T_PartitionBoundSpec:
loc = ((const PartitionBoundSpec *) expr)->location;
break;
case T_PartitionRangeDatum:
loc = ((const PartitionRangeDatum *) expr)->location;
break;
default: default:
/* for any other node type it's just unknown... */ /* for any other node type it's just unknown... */
loc = -1; loc = -1;
......
...@@ -2392,6 +2392,8 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) ...@@ -2392,6 +2392,8 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation); WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts); WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations); WRITE_NODE_FIELD(inhRelations);
WRITE_NODE_FIELD(partspec);
WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(ofTypename); WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints); WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options); WRITE_NODE_FIELD(options);
...@@ -3277,6 +3279,47 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node) ...@@ -3277,6 +3279,47 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]); 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 - * outNode -
...@@ -3865,6 +3908,18 @@ outNode(StringInfo str, const void *obj) ...@@ -3865,6 +3908,18 @@ outNode(StringInfo str, const void *obj)
case T_TriggerTransition: case T_TriggerTransition:
_outTriggerTransition(str, obj); _outTriggerTransition(str, obj);
break; 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: default:
......
...@@ -2265,6 +2265,36 @@ _readExtensibleNode(void) ...@@ -2265,6 +2265,36 @@ _readExtensibleNode(void)
READ_DONE(); 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 * parseNodeString
* *
...@@ -2497,6 +2527,10 @@ parseNodeString(void) ...@@ -2497,6 +2527,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan(); return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14)) else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode(); return_value = _readExtensibleNode();
else if (MATCH("PARTITIONBOUND", 14))
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTRANGEDATUM", 14))
return_value = _readPartitionRangeDatum();
else else
{ {
elog(ERROR, "badly formatted node string \"%.32s\"...", token); elog(ERROR, "badly formatted node string \"%.32s\"...", token);
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "catalog/catalog.h" #include "catalog/catalog.h"
#include "catalog/dependency.h" #include "catalog/dependency.h"
#include "catalog/heap.h" #include "catalog/heap.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h" #include "catalog/pg_am.h"
#include "foreign/fdwapi.h" #include "foreign/fdwapi.h"
#include "miscadmin.h" #include "miscadmin.h"
...@@ -1140,6 +1141,7 @@ get_relation_constraints(PlannerInfo *root, ...@@ -1140,6 +1141,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid; Index varno = rel->relid;
Relation relation; Relation relation;
TupleConstr *constr; TupleConstr *constr;
List *pcqual;
/* /*
* We assume the relation has already been safely locked. * We assume the relation has already been safely locked.
...@@ -1225,6 +1227,24 @@ get_relation_constraints(PlannerInfo *root, ...@@ -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); heap_close(relation, NoLock);
return result; return result;
......
...@@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) ...@@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */ /* Process ON CONFLICT, if any. */
if (stmt->onConflictClause) 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, qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause); stmt->onConflictClause);
}
/* /*
* If we have a RETURNING clause, we need to add the target relation to * If we have a RETURNING clause, we need to add the target relation to
......
This diff is collapsed.
...@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) ...@@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions"); err = _("grouping operations are not allowed in trigger WHEN conditions");
break; 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 * There is intentionally no default: case here, so that the
...@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, ...@@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN: case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions"); err = _("window functions are not allowed in trigger WHEN conditions");
break; 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 * There is intentionally no default: case here, so that the
......
...@@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) ...@@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN: case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition"); err = _("cannot use subquery in trigger WHEN condition");
break; 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 * There is intentionally no default: case here, so that the
...@@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind) ...@@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE"; return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN: case EXPR_KIND_TRIGGER_WHEN:
return "WHEN"; return "WHEN";
case EXPR_KIND_PARTITION_EXPRESSION:
return "PARTITION BY";
/* /*
* There is intentionally no default: case here, so that the * There is intentionally no default: case here, so that the
......
...@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location) ...@@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location)
case EXPR_KIND_TRIGGER_WHEN: case EXPR_KIND_TRIGGER_WHEN:
err = _("set-returning functions are not allowed in trigger WHEN conditions"); err = _("set-returning functions are not allowed in trigger WHEN conditions");
break; 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 * There is intentionally no default: case here, so that the
......
This diff is collapsed.
...@@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename, ...@@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename,
*/ */
if (event_relation->rd_rel->relkind != RELKIND_RELATION && if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW && 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, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view", errmsg("\"%s\" is not a table or view",
......
...@@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, ...@@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle; TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION || 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. * Emit CTID so that executor can find the row to update or delete.
......
...@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, ...@@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
*hasSubLinks = false; *hasSubLinks = false;
/* If this is not a normal relation, just return immediately */ /* 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; return;
/* Switch to checkAsUser if it's set */ /* Switch to checkAsUser if it's set */
......
...@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate, ...@@ -987,7 +987,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */ /* Create the table itself */
address = DefineRelation((CreateStmt *) stmt, address = DefineRelation((CreateStmt *) stmt,
RELKIND_RELATION, RELKIND_RELATION,
InvalidOid, NULL); InvalidOid, NULL,
queryString);
EventTriggerCollectSimpleCommand(address, EventTriggerCollectSimpleCommand(address,
secondaryObject, secondaryObject,
stmt); stmt);
...@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate, ...@@ -1020,7 +1021,8 @@ ProcessUtilitySlow(ParseState *pstate,
/* Create the table itself */ /* Create the table itself */
address = DefineRelation((CreateStmt *) stmt, address = DefineRelation((CreateStmt *) stmt,
RELKIND_FOREIGN_TABLE, RELKIND_FOREIGN_TABLE,
InvalidOid, NULL); InvalidOid, NULL,
queryString);
CreateForeignTable((CreateForeignTableStmt *) stmt, CreateForeignTable((CreateForeignTableStmt *) stmt,
address.objectId); address.objectId);
EventTriggerCollectSimpleCommand(address, EventTriggerCollectSimpleCommand(address,
......
This diff is collapsed.
This diff is collapsed.
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h" #include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h" #include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h" #include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "catalog/pg_range.h" #include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h" #include "catalog/pg_rewrite.h"
...@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = { ...@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
}, },
8 8
}, },
{PartitionedRelationId, /* PARTRELID */
PartitionedRelidIndexId,
1,
{
Anum_pg_partitioned_table_partrelid,
0,
0,
0
},
32
},
{ProcedureRelationId, /* PROCNAMEARGSNSP */ {ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId, ProcedureNameArgsNspIndexId,
3, 3,
......
...@@ -68,6 +68,8 @@ static int numextmembers; ...@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables, static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits); 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 void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs, static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize); Size objSize);
...@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2); ...@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2); static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self, static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits); InhInfo *inhinfo, int numInherits);
static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size); static int strInArray(const char *pattern, char **arr, int arr_size);
...@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) ...@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo; NamespaceInfo *nspinfo;
ExtensionInfo *extinfo; ExtensionInfo *extinfo;
InhInfo *inhinfo; InhInfo *inhinfo;
PartInfo *partinfo;
int numAggregates; int numAggregates;
int numInherits; int numInherits;
int numPartitions;
int numRules; int numRules;
int numProcLangs; int numProcLangs;
int numCasts; int numCasts;
...@@ -231,6 +237,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) ...@@ -231,6 +237,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading table inheritance information\n"); write_msg(NULL, "reading table inheritance information\n");
inhinfo = getInherits(fout, &numInherits); inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
write_msg(NULL, "reading partition information\n");
partinfo = getPartitions(fout, &numPartitions);
if (g_verbose) if (g_verbose)
write_msg(NULL, "reading event triggers\n"); write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers); getEventTriggers(fout, &numEventTriggers);
...@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr) ...@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n"); write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits); 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) if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n"); write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables); getTableAttrs(fout, tblinfo, numTables);
...@@ -273,6 +288,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) ...@@ -273,6 +288,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading policies\n"); write_msg(NULL, "reading policies\n");
getPolicies(fout, tblinfo, numTables); getPolicies(fout, tblinfo, numTables);
if (g_verbose)
write_msg(NULL, "reading partition key information for interesting tables\n");
getTablePartitionKeyInfo(fout, tblinfo, numTables);
*numTablesPtr = numTables; *numTablesPtr = numTables;
return tblinfo; return tblinfo;
} }
...@@ -319,6 +338,43 @@ flagInhTables(TableInfo *tblinfo, int numTables, ...@@ -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 - /* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes * for each dumpable table in tblinfo, flag its inherited attributes
* *
...@@ -919,6 +975,40 @@ findParentsByOid(TableInfo *self, ...@@ -919,6 +975,40 @@ findParentsByOid(TableInfo *self,
self->parents = NULL; 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 * parseOidArray
* parse a string of numbers delimited by spaces into a character array * parse a string of numbers delimited by spaces into a character array
......
This diff is collapsed.
...@@ -312,6 +312,7 @@ typedef struct _tableInfo ...@@ -312,6 +312,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */ bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */ struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */ struct _constraintInfo *checkexprs; /* CHECK constraints */
char *partkeydef; /* partition key definition */
/* /*
* Stuff computed only for dumpable tables. * Stuff computed only for dumpable tables.
...@@ -321,6 +322,8 @@ typedef struct _tableInfo ...@@ -321,6 +322,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */ struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */ int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */ struct _triggerInfo *triggers; /* array of TriggerInfo structs */
struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
char *partitiondef; /* partition key definition */
} TableInfo; } TableInfo;
typedef struct _attrDefInfo typedef struct _attrDefInfo
...@@ -459,6 +462,15 @@ typedef struct _inhInfo ...@@ -459,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */ Oid inhparent; /* OID of its parent */
} InhInfo; } 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 typedef struct _prsInfo
{ {
DumpableObject dobj; DumpableObject dobj;
...@@ -625,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions); ...@@ -625,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables); extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables); extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits); 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 getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables); extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules); extern RuleInfo *getRules(Archive *fout, int *numRules);
...@@ -649,5 +662,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[], ...@@ -649,5 +662,6 @@ extern void processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
int numExtensions); int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers); extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables); extern void getPolicies(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getTablePartitionKeyInfo(Archive *fout, TableInfo *tblinfo, int numTables);
#endif /* PG_DUMP_H */ #endif /* PG_DUMP_H */
This diff is collapsed.
...@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = { ...@@ -452,7 +452,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */ /* catname */
"pg_catalog.pg_class c", "pg_catalog.pg_class c",
/* selcondition */ /* selcondition */
"c.relkind IN ('r')", "c.relkind IN ('r', 'P')",
/* viscondition */ /* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)", "pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */ /* namespace */
...@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = { ...@@ -483,7 +483,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */ /* catname */
"pg_catalog.pg_class c", "pg_catalog.pg_class c",
/* selcondition */ /* selcondition */
"c.relkind IN ('r', 'f', 'v')", "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */ /* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)", "pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */ /* namespace */
...@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = { ...@@ -513,7 +513,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */ /* catname */
"pg_catalog.pg_class c", "pg_catalog.pg_class c",
/* selcondition */ /* selcondition */
"c.relkind IN ('r', 'S', 'v', 'm', 'f')", "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */ /* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)", "pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */ /* namespace */
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201612061 #define CATALOG_VERSION_NO 201612071
#endif #endif
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment